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

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

## 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](#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 theme={null}
https://media.rhombussystems.com/media/{cameraUuid}/{mediaRegion}/snapshot.jpeg
```

<Note>
  Todos los endpoints de `media.rhombussystems.com` usan la misma autenticación que la API principal — incluye los encabezados `x-auth-scheme` y `x-auth-apikey` en cada solicitud.
</Note>

Para obtener el `cameraUuid` y el `mediaRegion` de una cámara, llama a `POST /api/camera/getMinimalCameraStateList`. La respuesta contiene un arreglo `cameraStates`; cada entrada tiene los campos `uuid` y `mediaRegion`.

### Implementación básica

```bash cURL theme={null}
# 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 theme={null}
# 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`. La respuesta contiene un `frameUri` — haz una solicitud GET a ese URI con los mismos encabezados de autenticación para descargar el fotograma.

```javascript theme={null}
async function getFrameAtTime(cameraUuid, timestampMs, apiKey) {
  // Get a signed frame URI
  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`. 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 y luego coloca la URL devuelta dentro de un iframe. El reproductor del iframe maneja la autenticación a través de la propia URL, por lo que no se necesita una API key en el navegador.

```javascript theme={null}
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 theme={null}
<iframe
  allow="fullscreen"
  frameborder="0"
  height="100%"
  width="100%"
  src="SHARED_STREAM_URL_GOES_HERE">
</iframe>
```

### Ejemplo de componente React

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

const RhombusSharedStream = ({
  cameraUuid,
  apiKey,
  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);
        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 })
        });

        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, apiKey, 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 theme={null}
https://console.rhombussystems.com/share/live/ehrF58ABSj2VT8QzXh7GVA?disableautoplay=true&hideevents=true&realtime=true
```

#### Uso en React con parámetros

```jsx theme={null}
<RhombusSharedStream
  cameraUuid="your-camera-uuid"
  apiKey="your-api-key"
  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 y está firmado con un token en la URL — no se necesita el encabezado `x-auth-apikey` para obtenerlo.

<Note>
  La URL HLS está limitada a una sola organización y tiene un tiempo de expiración. Trátala como una credencial y no la expongas en páginas públicas sin una política de expiración.
</Note>

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

```html theme={null}
<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, 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 { 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', 'YOUR_API_KEY');
</script>
```

Como la creación de una transmisión compartida requiere una API key, ejecuta la llamada a `createSharedLiveVideoStream` desde un servidor que controles y pasa al navegador únicamente la `sharedLiveM3U8StreamUrl` (o `sharedLiveVideoStreamUrl`) resultante. Mantén la API key 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` 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` para hacer seguimiento del procesamiento — cada clip informa `percentComplete` y `status` — y recupera el clip finalizado una vez que esté listo.

```bash cURL theme={null}
# 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 theme={null}
# 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](/es/rhombus-cli) también puede descargar grabaciones sin que tengas que implementar este flujo manualmente:

```bash theme={null}
# Descarga la última hora de grabaciones de una cámara como MP4
rhombus footage --camera CAMERA_UUID --duration 1h --output footage.mp4

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

Para flujos basados en alertas, configura un [webhook](/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` — 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:

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

const RhombusCameraViewer = ({
  cameraUuid,
  apiKey,
  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 () => {
    const response = await fetch('https://api2.rhombussystems.com/api/camera/getMinimalCameraStateList', {
      method: 'POST',
      headers: {
        'x-auth-scheme': 'api-token',
        'x-auth-apikey': apiKey,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({})
    });

    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, apiKey]);

  const loadThumbnail = useCallback(async (camera) => {
    const response = await fetch(
      `https://media.rhombussystems.com/media/${camera.uuid}/${camera.mediaRegion}/snapshot.jpeg`,
      { headers: { 'x-auth-scheme': 'api-token', 'x-auth-apikey': apiKey } }
    );
    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);
  }, [apiKey]);

  const createSharedStream = useCallback(async () => {
    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 })
    });
    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, apiKey, streamOptions]);

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

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

        if (viewType === 'thumbnail') {
          await loadThumbnail(camera);
          if (autoRefresh) {
            interval = setInterval(() => {
              loadThumbnail(camera).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, 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 theme={null}
// Vista de miniatura con auto-refresco
<RhombusCameraViewer
  cameraUuid="your-camera-uuid"
  apiKey="your-api-key"
  viewType="thumbnail"
  autoRefresh={true}
  refreshInterval={3000}
  width="640px"
  height="480px"
/>

// Transmisión en vivo con opciones personalizadas
<RhombusCameraViewer
  cameraUuid="your-camera-uuid"
  apiKey="your-api-key"
  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 API keys 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.
* **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 API key a los permisos mínimos necesarios.
* **Establece expiraciones para las transmisiones compartidas**: Pasa `expirationTimeSecs` al crear transmisiones compartidas para que las URLs no sobrevivan al caso de uso.

### Manejo de errores

```javascript theme={null}
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 theme={null}
<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 la API key tenga acceso a la ubicación de la cámara.
* 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

<CardGroup cols={2}>
  <Card title="Reproductor de video" icon="play" href="/implementations/video-player">
    Crea un reproductor de video personalizado con reproducción HLS y controles de línea de tiempo.
  </Card>

  <Card title="Respaldo de grabaciones" icon="download" href="/implementations/backup-footage-local-storage">
    Descarga y archiva grabaciones de cámara en almacenamiento local.
  </Card>
</CardGroup>
