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

# Transmite video en vivo y grabado desde cámaras

> Incrusta video en vivo y grabado de cámaras Rhombus en tus aplicaciones con transmisiones HLS, miniaturas y URLs de reproducción compartidas vía API.

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

## Descripción general

Las cámaras Rhombus exponen varias formas de acceder al contenido de video a través de la API pública:

- **Transmisión en vivo** para monitoreo en tiempo real, incrustada como un iframe alojado por Rhombus
- **Manifiestos HLS** (`.m3u8`) para reproducción directa en tu propio reproductor
- **Imágenes en miniatura** para paneles y vistas previas rápidas
- **Capturas de fotogramas** en cualquier marca de tiempo histórica

Para descargar grabaciones como archivos MP4, consulta [Acceso a video grabado](https://developer.rhombus.com/es/implementations/streaming-video#acceso-a-video-grabado).

## Obtención de miniaturas de cámara

Todas las cámaras Rhombus suben automáticamente miniaturas. Son ideales para paneles, interfaces de monitoreo y verificaciones rápidas de estado.

### Endpoint de miniaturas

Recupera las miniaturas de la cámara mediante una solicitud GET a:

```text
https://media.rhombussystems.com/media/{cameraUuid}/{mediaRegion}/snapshot.jpeg
```

> **Note**: Las URL de medios no llevan ninguna credencial, así que autentica cada solicitud a `media.rhombussystems.com`. Desde un servidor, envía los encabezados de tu clave de API (`x-auth-scheme: api-token` y `x-auth-apikey`), o `Authorization: Bearer <token>` para un access token de OAuth cuyo grant incluya el scope `read`. En un navegador, añade en su lugar un [token federado](https://developer.rhombus.com/es/authentication/federated-tokens) como parámetros de consulta: `?x-auth-scheme=federated-token&x-auth-ft=<token>` (los clientes que pueden definir encabezados pueden enviar los mismos nombres como encabezados). Consulta [Medios y transmisiones](https://developer.rhombus.com/es/authentication/media).

Para obtener el `cameraUuid` y el `mediaRegion` de una cámara, llama a [`POST /api/camera/getMinimalCameraStateList`](https://developer.rhombus.com/api-reference/camera-webservice/get-minimal-camera-state-list). La respuesta contiene un arreglo `cameraStates`; cada entrada tiene los campos `uuid` y `mediaRegion`.

### Implementación básica

```bash cURL
# 1. Lista las cámaras para obtener UUIDs y regiones de medios
curl -X POST \
  "https://api2.rhombussystems.com/api/camera/getMinimalCameraStateList" \
  -H "x-auth-scheme: api-token" \
  -H "x-auth-apikey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{}'
```

```bash cURL
# 2. Obtén la miniatura usando el UUID y la región de medios de una cámara
curl -X GET \
  "https://media.rhombussystems.com/media/{CAMERA_UUID}/{MEDIA_REGION}/snapshot.jpeg" \
  -H "x-auth-scheme: api-token" \
  -H "x-auth-apikey: YOUR_API_KEY" \
  --output thumbnail.jpg
```

### Obtener fotogramas en momentos específicos

Para obtener una imagen JPEG en un momento histórico específico, llama a [`POST /api/video/getExactFrameUri`](https://developer.rhombus.com/api-reference/video-webservice/get-exact-frame-uri). La respuesta contiene un `frameUri`. No está firmado ni lleva ninguna credencial: descárgalo con los mismos encabezados de autenticación desde un servidor, o añade los parámetros de consulta del token federado en un navegador.

```javascript
// Server-side: this uses your API key
async function getFrameAtTime(cameraUuid, timestampMs, apiKey) {
  // Get the frame URI (it isn't signed, so the download needs auth too)
  const frameUriResponse = await fetch('https://api2.rhombussystems.com/api/video/getExactFrameUri', {
    method: 'POST',
    headers: {
      'x-auth-scheme': 'api-token',
      'x-auth-apikey': apiKey,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      cameraUuid: cameraUuid,
      timestampMs: timestampMs // epoch milliseconds
    })
  });

  const { frameUri } = await frameUriResponse.json();

  // Fetch the actual JPEG
  const frameResponse = await fetch(frameUri, {
    headers: {
      'x-auth-scheme': 'api-token',
      'x-auth-apikey': apiKey
    }
  });

  return frameResponse.blob();
}
```

La solicitud también acepta los campos opcionales `downscaleFactor`, `jpgQuality` y `permyriadCropX`/`permyriadCropY`/`permyriadCropWidth`/`permyriadCropHeight` (cada uno en milésimas de las dimensiones de la imagen, donde `10000` = 100%).

## Transmisión de video en vivo

Rhombus ofrece dos formas de reproducir video en vivo en tu aplicación:

1. **Incrustar el reproductor Rhombus como iframe** — la opción más sencilla; los analíticos y eventos de la línea de tiempo se renderizan automáticamente.
2. **Reproducir el manifiesto HLS directamente** — usa tu propio reproductor (`hls.js`, Safari nativo, etc.) para tener control total de la UI.

Ambas opciones provienen de la misma llamada a la API: [`POST /api/camera/createSharedLiveVideoStream`](https://developer.rhombus.com/api-reference/camera-webservice/create-shared-live-video-stream). La respuesta contiene ambas URLs:

| Campo                       | Descripción                                                                       |
| --------------------------- | --------------------------------------------------------------------------------- |
| `sharedLiveVideoStreamUrl`  | URL del reproductor alojado por Rhombus — incrústala en un iframe                 |
| `sharedLiveM3U8StreamUrl`   | Manifiesto HLS (`.m3u8`) — reprodúcelo directamente con cualquier reproductor HLS |
| `sharedLiveVideoStreamUuid` | UUID del recurso compartido creado, útil para revocarlo más tarde                 |

### Incrustar transmisiones compartidas como iframes (Recomendado)

Crea una transmisión compartida en tu servidor y luego coloca la URL devuelta dentro de un iframe. El navegador no necesita ninguna credencial de Rhombus porque el propio enlace da acceso: cualquiera que lo tenga puede ver el video, salvo que lo crees con [`createProtectedSharedLiveVideoStream`](https://developer.rhombus.com/api-reference/camera-webservice/create-protected-shared-live-video-stream) (protegido con contraseña). Establece `expirationTimeSecs` y elimina el recurso compartido cuando ya no lo necesites. Consulta [Enlaces de transmisión compartida](https://developer.rhombus.com/es/authentication/media#enlaces-de-transmisión-compartida).

```javascript
// Server-side: this uses your API key
async function createSharedStream(cameraUuid, apiKey) {
  const response = await fetch('https://api2.rhombussystems.com/api/camera/createSharedLiveVideoStream', {
    method: 'POST',
    headers: {
      'x-auth-scheme': 'api-token',
      'x-auth-apikey': apiKey,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({ cameraUuid })
  });

  const data = await response.json();
  return data.sharedLiveVideoStreamUrl;
}
```

Incrusta la URL devuelta:

```html
<iframe
  allow="fullscreen"
  frameborder="0"
  height="100%"
  width="100%"
  src="SHARED_STREAM_URL_GOES_HERE">
</iframe>
```

### Ejemplo de componente React

El componente llama a `/api/shared-stream`, una ruta de tu propio backend que llama a `createSharedLiveVideoStream` con tu clave de API y devuelve la respuesta.

```jsx
import React, { useEffect, useState } from 'react';

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

  useEffect(() => {
    let cancelled = false;

    (async () => {
      try {
        setLoading(true);
        // Your backend creates the share with its API key and returns the response
        const response = await fetch('/api/shared-stream', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ cameraUuid })
        });

        if (!response.ok) throw new Error('Failed to create shared stream');
        const data = await response.json();

        const url = new URL(data.sharedLiveVideoStreamUrl);
        Object.entries(options).forEach(([key, value]) => {
          url.searchParams.set(key, value);
        });

        if (!cancelled) {
          setStreamUrl(url.toString());
          setError(null);
        }
      } catch (err) {
        if (!cancelled) setError(err.message);
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();

    return () => { cancelled = true; };
  }, [cameraUuid, options]);

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

### Parámetros de URL para control mejorado

Personaliza el reproductor incrustado mediante parámetros de URL. Todos los parámetros pueden establecerse en `true` o `false`:

| Parámetro         | Descripción                                                          |
| ----------------- | -------------------------------------------------------------------- |
| `disableautoplay` | Activa o desactiva la reproducción automática al cargar              |
| `hideevents`      | Activa o desactiva la línea de tiempo y los eventos relacionados     |
| `realtime`        | Activa o desactiva la transmisión en tiempo real por defecto         |
| `showheader`      | Activa o desactiva los botones de zoom y transmisión arriba          |
| `showfooter`      | Activa o desactiva el nombre de la cámara y la marca de tiempo abajo |
| `lowRes`          | Fuerza baja resolución en la transmisión de video                    |
| `performanceMode` | Fuerza el modo de rendimiento permitiendo el uso de GPU              |
| `t`               | Marca de tiempo (milisegundos epoch) donde debe iniciar el video     |

#### Uso de parámetros

Anexa los parámetros a la URL de la transmisión compartida: `https://{{url}}/?{{variable}}=true&{{variable}}=false`

**Ejemplo:**

```text
https://console.rhombussystems.com/share/live/ehrF58ABSj2VT8QzXh7GVA?disableautoplay=true&hideevents=true&realtime=true
```

#### Uso en React con parámetros

```jsx
<RhombusSharedStream
  cameraUuid="your-camera-uuid"
  options={{
    disableautoplay: 'true',
    hideevents: 'true',
    realtime: 'true',
    showheader: 'false',
    lowRes: 'false'
  }}
/>
```

### Incrustar transmisiones HLS directamente

Si quieres renderizar video en vivo en tu propio reproductor en lugar del iframe alojado, usa `sharedLiveM3U8StreamUrl` de la misma respuesta de `createSharedLiveVideoStream`. El manifiesto es un `.m3u8` HLS estándar. No está firmado: la URL solo contiene el ID del recurso compartido, y cualquiera que la tenga puede ver el video, así que no se necesita el encabezado `x-auth-apikey`. Para un recurso compartido protegido con contraseña, envía `Authorization: Basic <base64(":" + password)>` (con el nombre de usuario vacío).

> **Note**: La URL HLS es un enlace al portador para una cámara: salvo que el recurso compartido esté protegido con contraseña, cualquiera que la tenga puede ver el video hasta que expire o elimines el recurso compartido. Solo expira si estableces `expirationTimeSecs` (una hora Unix en segundos, no una duración). Trátala como una credencial.

Safari reproduce HLS de forma nativa. Para otros navegadores, usa [hls.js](https://github.com/video-dev/hls.js):

```html
<video id="rhombus-live" controls autoplay muted style="width: 100%;"></video>

<script type="module">
  import Hls from 'https://cdn.jsdelivr.net/npm/hls.js@latest/dist/hls.min.js';

  async function startLive(cameraUuid) {
    // Your backend creates the share with its API key and returns the response
    const response = await fetch('/api/shared-stream', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ cameraUuid })
    });

    const { sharedLiveM3U8StreamUrl } = await response.json();
    const video = document.getElementById('rhombus-live');

    if (Hls.isSupported()) {
      const hls = new Hls();
      hls.loadSource(sharedLiveM3U8StreamUrl);
      hls.attachMedia(video);
    } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
      video.src = sharedLiveM3U8StreamUrl;
    }
  }

  startLive('YOUR_CAMERA_UUID');
</script>
```

Para reproducir las transmisiones propias de una cámara en lugar de un recurso compartido, llama a [`getMediaUris`](https://developer.rhombus.com/api-reference/camera-webservice/get-camera-media-uris) en tu servidor y entrega al reproductor `wanLiveM3u8Uri` (o el DASH `wanLiveMpdUri`) con un token federado añadido como parámetros de consulta. En los hosts de la nube, las listas de reproducción HLS y los manifiestos DASH (en vivo y grabados) llevan esa cadena de consulta a cada URL de segmento que enumeran, así que esa única URL funciona en cualquier reproductor. Las claves de API y `x-auth-org` nunca se copian en las URL de segmento. Consulta [Medios y transmisiones](https://developer.rhombus.com/es/authentication/media#video-en-vivo-y-grabado).

Como la creación de una transmisión compartida requiere una clave de API, `/api/shared-stream` es una ruta de un servidor que controlas: llama a `createSharedLiveVideoStream` y pasa al navegador únicamente la `sharedLiveM3U8StreamUrl` (o `sharedLiveVideoStreamUrl`) resultante. Mantén la clave de API fuera del código del cliente.

## Acceso a video grabado

Para exportar grabaciones como un clip, usa la API pública de clips. La creación de clips es **asíncrona**:

1. Llama a [`POST /api/video/spliceV3`](https://developer.rhombus.com/api-reference/video-webservice/splice-v3) con las cámaras y el rango de tiempo que quieras. Proporciona `deviceUuids` (arreglo), `startTimeMillis` (milisegundos desde la época) y `durationSec`. Establece `audioIncluded: true` para incluir el audio de un audio gateway asociado. La respuesta devuelve un `clipUuid`.
2. Consulta [`POST /api/event/getClipsWithProgress`](https://developer.rhombus.com/api-reference/event-webservice/get-list-of-saved-clips-in-organization-with-current-progress) para hacer seguimiento del procesamiento — cada clip informa `percentComplete` y `status` — y recupera el clip finalizado una vez que esté listo.

```bash cURL
# 1. Solicita un clip para un rango de tiempo
curl -X POST \
  "https://api2.rhombussystems.com/api/video/spliceV3" \
  -H "x-auth-scheme: api-token" \
  -H "x-auth-apikey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{
    "deviceUuids": ["CAMERA_UUID"],
    "startTimeMillis": 1700000000000,
    "durationSec": 60,
    "title": "Clip de incidente"
  }'
```

```bash cURL
# 2. Consulta el progreso hasta que el clip esté listo
curl -X POST \
  "https://api2.rhombussystems.com/api/event/getClipsWithProgress" \
  -H "x-auth-scheme: api-token" \
  -H "x-auth-apikey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  --data '{}'
```

Como conveniencia, la [Rhombus CLI](https://developer.rhombus.com/es/rhombus-cli) también puede reproducir grabaciones y descargar clips de alertas sin que tengas que implementar este flujo manualmente:

```bash
# Abre las grabaciones de una cámara en tu navegador, desde hace una hora
rhombus footage CAMERA_UUID --start "1h ago"

# Descarga el clip asociado a una alerta específica
rhombus alert download ALERT_UUID --output alert.mp4
```

Para flujos basados en alertas, configura un [webhook](https://developer.rhombus.com/es/webhooks) para entregar eventos de alerta a tu servicio y luego crea un clip para el rango de tiempo de la alerta.

Para listar alertas recientes de forma programática, usa [`POST /api/event/getPolicyAlerts`](https://developer.rhombus.com/api-reference/event-webservice/get-recent-policy-alerts-for-organization) — el endpoint anterior `getPolicyAlertsV2` está obsoleto.

## Ejemplo de implementación completa

Un componente React que admite tanto vista de miniatura como transmisión en vivo. Tu clave de API se queda en tu backend, que sirve tres rutas, cada una con una llamada a Rhombus: `/api/camera-states` (`getMinimalCameraStateList`), `/api/federated-token` (`generateFederatedSessionToken`, como en [Tokens federados](https://developer.rhombus.com/es/authentication/federated-tokens#generar-un-token), que devuelve la respuesta de generación con su `expiresAtMs`; `durationSec` vale una hora de forma predeterminada y no puede superar las 24 horas) y `/api/shared-stream` (`createSharedLiveVideoStream`):

```jsx
import React, { useEffect, useRef, useState, useCallback } from 'react';

const RhombusCameraViewer = ({
  cameraUuid,
  viewType = 'thumbnail', // 'thumbnail' | 'stream'
  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);
  const lastObjectUrl = useRef(null);

  const getCameraDetails = useCallback(async () => {
    // Your backend calls getMinimalCameraStateList with its API key
    const response = await fetch('/api/camera-states', { method: 'POST' });

    if (!response.ok) throw new Error('Failed to list cameras');
    const data = await response.json();
    const camera = (data.cameraStates || []).find(c => c.uuid === cameraUuid);
    if (!camera) throw new Error('Camera not found');
    return camera;
  }, [cameraUuid]);

  // Your backend mints a short-lived federated token for this camera and
  // returns the mint response, including expiresAtMs
  const getFederatedToken = useCallback(async () => {
    const response = await fetch('/api/federated-token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ cameraUuid })
    });
    if (!response.ok) throw new Error('Failed to get a token');
    const { federatedSessionToken, expiresAtMs } = await response.json();
    return { token: federatedSessionToken, expiresAtMs };
  }, [cameraUuid]);

  const loadThumbnail = useCallback(async (camera, token) => {
    const auth = `x-auth-scheme=federated-token&x-auth-ft=${encodeURIComponent(token)}`;
    const response = await fetch(
      `https://media.rhombussystems.com/media/${camera.uuid}/${camera.mediaRegion}/snapshot.jpeg?${auth}`,
      { cache: 'no-store' }
    );
    if (!response.ok) throw new Error('Failed to load thumbnail');

    const blob = await response.blob();
    const url = URL.createObjectURL(blob);
    if (lastObjectUrl.current) URL.revokeObjectURL(lastObjectUrl.current);
    lastObjectUrl.current = url;
    setThumbnailUrl(url);
  }, []);

  const createSharedStream = useCallback(async () => {
    // Your backend creates the share with its API key and returns the response
    const response = await fetch('/api/shared-stream', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ cameraUuid })
    });
    if (!response.ok) throw new Error('Failed to create shared stream');

    const data = await response.json();
    const url = new URL(data.sharedLiveVideoStreamUrl);
    Object.entries(streamOptions).forEach(([key, value]) => {
      url.searchParams.set(key, value);
    });
    setStreamUrl(url.toString());
  }, [cameraUuid, streamOptions]);

  useEffect(() => {
    let cancelled = false;
    let interval = null;

    (async () => {
      try {
        setIsLoading(true);
        const camera = await getCameraDetails();
        if (cancelled) return;
        setCameraDetails(camera);

        if (viewType === 'thumbnail') {
          let auth = await getFederatedToken();
          await loadThumbnail(camera, auth.token);
          if (autoRefresh) {
            interval = setInterval(async () => {
              try {
                // Mint a new token a minute before this one expires
                if (Date.now() > auth.expiresAtMs - 60_000) auth = await getFederatedToken();
                await loadThumbnail(camera, auth.token);
              } catch (err) {
                setError(err.message);
              }
            }, refreshInterval);
          }
        } else if (viewType === 'stream') {
          await createSharedStream();
        }

        setError(null);
      } catch (err) {
        if (!cancelled) setError(err.message);
      } finally {
        if (!cancelled) setIsLoading(false);
      }
    })();

    return () => {
      cancelled = true;
      if (interval) clearInterval(interval);
      if (lastObjectUrl.current) {
        URL.revokeObjectURL(lastObjectUrl.current);
        lastObjectUrl.current = null;
      }
    };
  }, [viewType, autoRefresh, refreshInterval, getCameraDetails, getFederatedToken, loadThumbnail, createSharedStream]);

  if (error) {
    return (
      <div style={{ padding: '20px', border: '1px solid #ff6b6b', borderRadius: '4px' }}>
        <p>Error: {error}</p>
      </div>
    );
  }

  if (isLoading) {
    return <div style={{ padding: '20px', textAlign: 'center' }}>Loading camera...</div>;
  }

  return (
    <div style={{ width, height }}>
      {viewType === 'thumbnail' && thumbnailUrl && (
        <img
          src={thumbnailUrl}
          alt={cameraDetails?.name || `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`}
        />
      )}
    </div>
  );
};

export default RhombusCameraViewer;
```

### Ejemplos de uso

```jsx
// Vista de miniatura con auto-refresco
<RhombusCameraViewer
  cameraUuid="your-camera-uuid"
  viewType="thumbnail"
  autoRefresh={true}
  refreshInterval={3000}
  width="640px"
  height="480px"
/>

// Transmisión en vivo con opciones personalizadas
<RhombusCameraViewer
  cameraUuid="your-camera-uuid"
  viewType="stream"
  streamOptions={{
    disableautoplay: 'false',
    hideevents: 'false',
    realtime: 'true',
    showheader: 'true',
    showfooter: 'true'
  }}
  width="100%"
  height="600px"
/>
```

## Mejores prácticas

### Optimización del rendimiento

- **Caché de miniaturas**: Almacena las miniaturas en caché del lado del cliente y refréscalas en un intervalo razonable en lugar de hacer polling continuo.
- **Carga diferida**: Carga el contenido de video solo cuando sea necesario o cuando esté en el viewport.
- **Selección de calidad**: Usa `lowRes=true` en el reproductor del iframe para cuadrículas y paneles.

### Consideraciones de seguridad

- **Mantén las claves de API del lado del servidor**: Nunca expongas `x-auth-apikey` en código del navegador. Llama a `createSharedLiveVideoStream` desde tu backend y pasa al cliente solo las URLs resultantes. Para miniaturas y fotogramas en el navegador, usa un [token federado](https://developer.rhombus.com/es/authentication/federated-tokens).
- **Mantén los tokens federados de corta duración**: Genéralos por minutos u horas (nunca más de 24 horas), lee `expiresAtMs` en la respuesta de generación y genera uno nuevo antes de que se cumpla. Revoca un token que ya no necesites con `POST /api/org/revokeFederatedSessionToken`.
- **Solo HTTPS**: Usa siempre HTTPS para las llamadas a la API y para las URLs de transmisiones compartidas.
- **Limita el alcance de las claves**: Restringe cada clave de API a los permisos mínimos necesarios.
- **Establece expiraciones para las transmisiones compartidas**: Pasa `expirationTimeSecs`, la hora Unix en segundos en que el enlace deja de funcionar (no una duración), para que las URLs no sobrevivan al caso de uso. Sin él, un enlace compartido nunca expira.

### Manejo de errores

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

async function fetchWithRetry(url, init, retries = 3) {
  for (let attempt = 1; attempt <= retries; attempt++) {
    const controller = new AbortController();
    const timer = setTimeout(() => controller.abort(), 10000);
    try {
      const response = await fetch(url, { ...init, signal: controller.signal });
      clearTimeout(timer);
      if (!response.ok) {
        throw new RhombusVideoError(`HTTP ${response.status}`, response.status, init?.cameraUuid);
      }
      return response;
    } catch (error) {
      clearTimeout(timer);
      if (attempt === retries) throw error;
      await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
    }
  }
}
```

### Accesibilidad

```jsx
<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>
```

## Solución de problemas

**La miniatura no carga**

- Verifica que `cameraUuid` y `mediaRegion` provengan de una respuesta reciente de `getMinimalCameraStateList` — `mediaRegion` puede cambiar.
- Comprueba que el rol de la credencial tenga acceso a la ubicación de la cámara.
- En un navegador, revisa el token: un token federado expirado o revocado recibe 401, y un token generado con `domain` recibe 403 `DOMAIN_NOT_ALLOWED` en páginas de cualquier otro origen.
- Confirma que la cámara esté en línea (campo `connectionStatus` en la respuesta de estado de la cámara).

**Fallos de conexión de transmisión**

- Verifica la conectividad de red hacia `*.rhombussystems.com`.
- Para reproducción HLS, confirma que el navegador soporte HLS de forma nativa o que `hls.js` esté cargado.
- Revisa las reglas de firewall/proxy — los proxies corporativos suelen bloquear las solicitudes de segmentos de medios.

**Calidad de video deficiente**

- Pasa `lowRes=true` para clientes con ancho de banda limitado.
- Verifica el ancho de banda de subida en el sitio de la cámara.

## Próximos pasos

- **[Reproductor de video](https://developer.rhombus.com/es/implementations/video-player)**: Crea un reproductor de video personalizado con reproducción HLS y controles de línea de tiempo.
- **[Detección en tiempo real por LAN](https://developer.rhombus.com/es/implementations/lan-realtime-detection-overlay)**: Analiza los cuadros delimitadores de IA del stream WebSocket H.264 por LAN y superponlos sobre el video en vivo.
- **[Streaming por LAN](https://developer.rhombus.com/es/implementations/lan-streaming)**: Transmite video en vivo, grabaciones y audio directamente desde los dispositivos de tu red.
- **[Respaldo de grabaciones](https://developer.rhombus.com/es/implementations/backup-footage-local-storage)**: Descarga y archiva grabaciones de cámara en almacenamiento local.
