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

# Streaming por LAN

> Autentícate directamente con cámaras y gateways de audio de Rhombus en tu red local para video en vivo, grabaciones y audio bidireccional.

<Note>
  Esta página fue traducida automáticamente. Si encuentra errores o tiene sugerencias, [contáctenos](mailto:support@rhombus.com).
</Note>

## Descripción general

Cuando tu cliente está en la misma red que un dispositivo Rhombus, puede obtener video en vivo, grabaciones y audio directamente del dispositivo en lugar de hacerlo a través de la nube de Rhombus. Obtienes menor latencia y no consumes ancho de banda de internet.

<Warning>
  **Los dispositivos de tu LAN nunca aceptan API tokens.** Usa tu API token en tu servidor para generar un **token federado** de corta duración y luego presenta el token federado al dispositivo.
</Warning>

Esta guía cubre:

* En qué se diferencian los API tokens y los tokens federados, y cuál usar en cada caso
* Cómo generar un token federado y encontrar los URI LAN de un dispositivo
* Cómo autenticar solicitudes LAN con headers o parámetros de consulta
* H.264 en vivo y audio bidireccional por WebSocket, además de DASH/HLS para video en vivo y grabado
* Limitaciones de los navegadores y solución de problemas

## API tokens vs tokens federados

Son dos credenciales distintas con funciones distintas.

| | API token | Token federado |
| - | - | - |
| **Qué es** | El secreto de larga duración de tu integración, creado en la Rhombus Console | Un token de corta duración que generas con un API token mediante `POST /api/org/generateFederatedSessionToken` |
| **Duración** | Hasta que lo revoques | El `durationSec` que solicites |
| **Permisos** | El rol asignado al API token | El mismo rol que el API token que lo generó, opcionalmente limitado a un dispositivo |
| **Headers** | `x-auth-scheme: api-token` y `x-auth-apikey` | `x-auth-scheme: federated-token` y `x-auth-ft` |
| **Parámetros de consulta** | No soportados | `?x-auth-scheme=federated-token&x-auth-ft=TOKEN` (URL de medios y dispositivos LAN) |
| **API en la nube de Rhombus** | Sí | Sí, con headers (no los tokens limitados a un dispositivo) |
| **Medios en la nube de Rhombus** (URL de streams WAN) | Sí, con headers (clientes del lado del servidor) | Sí |
| **Dispositivos de tu LAN** | **No, nunca se aceptan** | Sí |
| **Dónde puede estar** | Solo en tu servidor | Navegadores, reproductores y clientes LAN |

Un token federado es una credencial al portador: cualquiera que lo tenga puede actuar con su rol. **Para streaming por LAN, genera tokens limitados a un dispositivo** con `deviceUUid`. Un token limitado a un dispositivo solo puede obtener los medios de ese dispositivo, y la API en la nube lo rechaza. Además, genéralo con un API token cuyo rol no sea más amplio de lo que el espectador necesita, y mantén `durationSec` corto.

<Note>
  El valor de `x-auth-scheme` es exactamente `federated-token`. No existe el esquema `federated-session-token`, y un token federado nunca va en `x-auth-apikey`.
</Note>

## Requisitos previos

<Note>
  * Una API key de Rhombus con permiso para ver las cámaras o gateways de audio que vas a transmitir ([obtén una aquí](/es/index))
  * Un cliente en la misma red que el dispositivo, capaz de alcanzarlo en el puerto TCP `8000`
  * Un dispositivo que pueda alcanzar la nube de Rhombus. El dispositivo verifica cada token nuevo con Rhombus, así que un dispositivo sin acceso a internet no puede autorizar clientes nuevos.
</Note>

## Implementación

<Steps>
  <Step title="Genera un token federado en tu servidor">
    Llama a `generateFederatedSessionToken` con tu API key. Tu API key nunca sale de tu servidor.

    <CodeGroup>
      ```python Python theme={null}
      import requests

      API_HEADERS = {
          "x-auth-scheme": "api-token",
          "x-auth-apikey": "YOUR_API_KEY",
          "Content-Type": "application/json",
      }

      response = requests.post(
          "https://api2.rhombussystems.com/api/org/generateFederatedSessionToken",
          headers=API_HEADERS,
          json={
              "durationSec": 3600,               # duración del token en segundos
              "deviceUUid": "YOUR_CAMERA_UUID",  # recomendado: solo los medios de este dispositivo
          },
      )
      response.raise_for_status()
      federated_token = response.json()["federatedSessionToken"]
      ```

      ```javascript Node.js theme={null}
      const response = await fetch(
        'https://api2.rhombussystems.com/api/org/generateFederatedSessionToken',
        {
          method: 'POST',
          headers: {
            'x-auth-scheme': 'api-token',
            'x-auth-apikey': process.env.RHOMBUS_API_KEY,
            'Content-Type': 'application/json'
          },
          body: JSON.stringify({ durationSec: 3600, deviceUUid: 'YOUR_CAMERA_UUID' })
        }
      );
      const { federatedSessionToken } = await response.json();
      ```

      ```bash cURL theme={null}
      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, "deviceUUid": "YOUR_CAMERA_UUID"}'
      ```
    </CodeGroup>

    Ejemplo de respuesta:

    ```json theme={null}
    {
      "federatedSessionToken": "Q2gP7m1kTnWb3sXyZ4aJ8w"
    }
    ```

    | Campo | Notas |
    | - | - |
    | `durationSec` | Obligatorio. Solicita solo la duración que necesita la sesión y genera un token nuevo antes de que expire. |
    | `deviceUUid` | Opcional, recomendado para LAN. Limita el token a los medios (LAN y WAN) de un dispositivo; el token no puede llamar a la API en la nube. Ten en cuenta las mayúsculas: `deviceUUid`. |
    | `domain` | Opcional. El origen del navegador autorizado a usar el token con los medios en la nube de Rhombus, como URL completa, por ejemplo `https://app.example.com`. Los dispositivos LAN no lo verifican, así que usa `deviceUUid` y un `durationSec` corto para limitar un token LAN. |

    El token nunca puede hacer más que el API token que lo generó. Si ese rol solo permite video en vivo, el dispositivo sirve streams en vivo y rechaza las grabaciones.
  </Step>

  <Step title="Obtén los URI LAN del dispositivo">
    En tu servidor, llama a `POST /api/camera/getMediaUris` con `cameraUuid`, o a `/api/audiogateway/getMediaUris` con `gatewayUuid`, usando tu API key. Un token federado limitado a un dispositivo no puede llamar a la API en la nube.

    ```python Python theme={null}
    response = requests.post(
        "https://api2.rhombussystems.com/api/camera/getMediaUris",
        headers=API_HEADERS,
        json={"cameraUuid": "YOUR_CAMERA_UUID"},
    )
    response.raise_for_status()
    uris = response.json()
    ```

    | Campo | Qué es | Ejemplo |
    | - | - | - |
    | `lanLiveH264Uris` | H.264 en vivo por WebSocket (la menor latencia) | `wss://192-168-1-50.lan.rhombussystems.com:8000/DEVICE_UUID/ws` |
    | `lanLiveOpusUris` | Audio bidireccional en vivo por WebSocket | `wss://192-168-1-50.lan.rhombussystems.com:8000/DEVICE_UUID/audio` |
    | `lanLiveMpdUris` / `lanLiveM3u8Uris` | DASH / HLS en vivo | `https://192-168-1-50.lan.rhombussystems.com:8000/DEVICE_UUID/live/live.mpd` |
    | `lanVodMpdUrisTemplates` / `lanVodM3u8UrisTemplates` | Grabaciones del almacenamiento propio del dispositivo | `https://192-168-1-50.lan.rhombussystems.com:8000/DEVICE_UUID/store/{START_TIME}_{DURATION}/clip.mpd` |
    | `lanCheckUrls` | Verificación de alcanzabilidad | `https://192-168-1-50.lan.rhombussystems.com:8000/DEVICE_UUID/` |

    * Cada campo es un arreglo porque un dispositivo puede tener más de una dirección LAN. Usa la primera que tu cliente pueda alcanzar.
    * En las plantillas VOD, reemplaza `{START_TIME}` por una marca de tiempo Unix en **segundos** y `{DURATION}` por una duración en segundos.
    * El nombre de host LAN es DNS público que resuelve a la IP privada del dispositivo, y el dispositivo sirve un certificado de confianza pública para él. Conéctate usando el nombre de host exactamente como se devuelve, no la IP directa, y mantén activada la verificación TLS normal. Los dispositivos de la región UE usan nombres de host `.lan.eu.rhombussystems.com` ([Regiones de API](/es/api-regions)).
  </Step>

  <Step title="Presenta el token federado al dispositivo">
    Envía el token federado en **cada** solicitud al dispositivo, incluido cada segmento DASH/HLS y el upgrade del WebSocket. Usa la forma que admita tu cliente:

    <Tabs>
      <Tab title="Headers">
        Para apps nativas, servidores, curl y ffmpeg:

        ```text theme={null}
        x-auth-scheme: federated-token
        x-auth-ft: YOUR_FEDERATED_TOKEN
        ```
      </Tab>

      <Tab title="Parámetros de consulta">
        Para conexiones `WebSocket` del navegador y reproductores que no pueden enviar headers:

        ```text theme={null}
        ?x-auth-scheme=federated-token&x-auth-ft=YOUR_FEDERATED_TOKEN
        ```
      </Tab>
    </Tabs>

    El dispositivo verifica el token con Rhombus: debe estar vigente, pertenecer a la organización del dispositivo y tener un rol que pueda ver este dispositivo. El dispositivo guarda en caché una verificación exitosa hasta por 60 segundos, así que un token expirado o revocado puede seguir funcionando hasta un minuto.
  </Step>
</Steps>

## Ejemplos de código

### Manifiesto DASH en vivo

```bash cURL theme={null}
TOKEN="YOUR_FEDERATED_TOKEN"

curl -H "x-auth-scheme: federated-token" -H "x-auth-ft: $TOKEN" \
  "https://192-168-1-50.lan.rhombussystems.com:8000/YOUR_CAMERA_UUID/live/live.mpd"
```

Un token válido devuelve el manifiesto DASH (`200`). Un token ausente, expirado o no autorizado devuelve `401`.

### Grabaciones con ffmpeg

ffmpeg pasa `-headers` a cada solicitud de playlist y de segmento. Esto guarda 60 segundos de grabación a partir de `1767225600` (2026-01-01 00:00:00 UTC):

```bash ffmpeg theme={null}
ffmpeg -headers $'x-auth-scheme: federated-token\r\nx-auth-ft: '"$TOKEN"$'\r\n' \
  -i "https://192-168-1-50.lan.rhombussystems.com:8000/YOUR_CAMERA_UUID/store/1767225600_60/clip.m3u8" \
  -c copy clip.mp4
```

### H.264 en vivo por WebSocket

<CodeGroup>
  ```python Python theme={null}
  import asyncio
  import json
  from urllib.parse import urlencode

  import requests
  import websockets  # pip install websockets

  API_HEADERS = {
      "x-auth-scheme": "api-token",
      "x-auth-apikey": "YOUR_API_KEY",
      "Content-Type": "application/json",
  }
  CAMERA_UUID = "YOUR_CAMERA_UUID"


  def mint_device_token(device_uuid: str) -> str:
      response = requests.post(
          "https://api2.rhombussystems.com/api/org/generateFederatedSessionToken",
          headers=API_HEADERS,
          json={"durationSec": 3600, "deviceUUid": device_uuid},
      )
      response.raise_for_status()
      return response.json()["federatedSessionToken"]


  def lan_h264_uri(camera_uuid: str) -> str:
      response = requests.post(
          "https://api2.rhombussystems.com/api/camera/getMediaUris",
          headers=API_HEADERS,
          json={"cameraUuid": camera_uuid},
      )
      response.raise_for_status()
      uris = response.json().get("lanLiveH264Uris") or []
      if not uris:
          raise SystemExit("La cámara no reportó ninguna dirección LAN")
      return uris[0]


  async def watch(uri: str, token: str, max_frames: int = 30) -> None:
      auth = urlencode({"x-auth-scheme": "federated-token", "x-auth-ft": token})
      async with websockets.connect(f"{uri}?{auth}") as ws:
          frames = 0
          async for message in ws:
              if isinstance(message, str):  # primero llega un mensaje de texto init
                  print("init:", json.loads(message))
                  continue
              timestamp_ms, offset = None, 0
              while offset + 4 <= len(message):
                  tlv_type = message[offset]
                  length = int.from_bytes(message[offset + 1 : offset + 4], "big")
                  value = message[offset + 4 : offset + 4 + length]
                  offset += 4 + length
                  if tlv_type == 0x02:
                      timestamp_ms = int.from_bytes(value, "big")
                  elif tlv_type in (0x00, 0x01):  # los datos del frame siempre van al final
                      kind = "keyframe" if tlv_type == 0x00 else "delta"
                      print(f"{kind}: {len(value)} bytes at {timestamp_ms}")
                      frames += 1
                      break
              if frames >= max_frames:
                  break


  asyncio.run(watch(lan_h264_uri(CAMERA_UUID), mint_device_token(CAMERA_UUID)))
  ```

  ```javascript Browser theme={null}
  // federatedToken viene de tu servidor (Paso 1), nunca tu API key
  const url = new URL(lanLiveH264Uri);
  url.searchParams.set('x-auth-scheme', 'federated-token');
  url.searchParams.set('x-auth-ft', federatedToken);

  const ws = new WebSocket(url);
  ws.binaryType = 'arraybuffer';
  ws.onmessage = (event) => {
    if (typeof event.data === 'string') {
      console.log('init', JSON.parse(event.data));
      return;
    }
    handleH264Message(event.data); // consulta el formato TLV enlazado abajo
  };
  ```
</CodeGroup>

Ejemplo de salida del script de Python:

```text theme={null}
init: {'action': 'init', 'width': 1920, 'height': 1080, 'codec': 'h264', 'framerate': 15}
keyframe: 48213 bytes at 1791158465584
delta: 3120 bytes at 1791158465651
```

Cada mensaje binario es una secuencia de registros TLV (tipo de 1 byte, longitud de 3 bytes big-endian, valor) que termina con los datos del frame H.264. La tabla completa de tipos, incluidas las detecciones de IA incrustadas, está en [Superposición de detección en tiempo real por LAN](/es/implementations/lan-realtime-detection-overlay). Para un reproductor de navegador listo para usar, usa `RhombusRealtimePlayer` con `connectionMode="lan"` del [React SDK](/es/implementations/react-sdk).

## Audio por WebSocket

El socket `lanLiveOpusUris` transporta audio en ambas direcciones y usa la misma autenticación con token federado.

### Escuchar

Cada mensaje binario del dispositivo contiene tres registros TLV. El encabezado de cada registro tiene 1 bit (bandera de eco), 7 bits (tipo) y una longitud de 24 bits big-endian, seguido del valor.

| Tipo | Valor |
| - | - |
| `0` | Cantidad de muestras (2 bytes, big-endian) |
| `1` | Marca de tiempo Unix en milisegundos (8 bytes, big-endian) |
| `4` | Un paquete Opus, 48 kHz mono. La bandera de eco se activa si el dispositivo estaba reproduciendo audio cuando se capturó. |

### Hablar

Para reproducir audio en el altavoz del dispositivo, envía mensajes binarios por el mismo socket. Cada registro es un tipo ASCII de 4 bytes, una longitud de 4 bytes big-endian y luego el contenido. Cada registro de audio contiene exactamente 20 ms de audio.

| Tipo | Contenido |
| - | - |
| `opus` | Un paquete Opus, 48 kHz mono |
| `pcm ` (con espacio al final) | 960 muestras PCM de 48 kHz mono, 16 bits con signo little-endian (longitud 1920) |
| `ctrl` | Marca de tiempo Unix de 8 bytes big-endian en milisegundos para iniciar la reproducción. Envía `0` para reproducir cada paquete aunque llegue tarde, o `-1` para descartar el audio atrasado y mantener el tiempo real. |

De forma predeterminada, el dispositivo reproduce cada paquete Opus, incluso los atrasados, y descarta el PCM atrasado para mantener el tiempo real.

## Aplicaciones de navegador

* **Los streams WebSocket** (`lanLiveH264Uris`, `lanLiveOpusUris`) funcionan desde cualquier origen con el token en la cadena de consulta.
* **DASH y HLS por LAN** (URL `lanLive*` y `lanVod*`) no se pueden cargar con JavaScript del navegador, como dash.js o hls.js, en tu propio dominio, porque los dispositivos solo permiten orígenes de Rhombus para solicitudes entre orígenes. Reproduce DASH/HLS por LAN desde una app nativa o un servidor, o usa el stream WebSocket en el navegador.
* Nunca pongas una API key en código de navegador. Genera tokens federados en tu servidor y entrega solo esos al navegador.

## Dispositivos con firmware anterior

Los dispositivos con firmware publicado antes de mediados de abril de 2026 solo leen el token desde una cookie. Si un cliente nativo debe admitirlos, envía también:

```text theme={null}
Cookie: RSESSIONID=RFT:YOUR_FEDERATED_TOKEN
```

El firmware actual también acepta esta cookie, así que un cliente nativo puede enviarla junto con los headers.

## Solución de problemas

<AccordionGroup>
  <Accordion title="401 en todas las solicitudes">
    Ningún token llegó al dispositivo, `x-auth-scheme` no es exactamente `federated-token`, o enviaste una API key en lugar de un token federado. Los dispositivos nunca aceptan API keys.
  </Accordion>

  <Accordion title="401 después de funcionar un tiempo">
    El token federado expiró. Genera uno nuevo antes de que se agote `durationSec`.
  </Accordion>

  <Accordion title="401 en un dispositivo, bien en los demás">
    El token se generó con `deviceUUid` para otro dispositivo, o su rol no puede ver este dispositivo.
  </Accordion>

  <Accordion title="El video en vivo funciona, las grabaciones se rechazan">
    El rol del token, heredado de la API key que lo generó, solo permite video en vivo.
  </Accordion>

  <Accordion title="La conexión se cierra sin respuesta">
    El dispositivo no pudo alcanzar la nube de Rhombus para verificar el token.
  </Accordion>

  <Accordion title="Funciona con curl, falla en un navegador">
    Los navegadores bloquean las solicitudes DASH/HLS entre orígenes hacia el dispositivo. Consulta [Aplicaciones de navegador](#aplicaciones-de-navegador).
  </Accordion>

  <Accordion title="Errores de TLS o de nombre de host">
    Usa el nombre de host LAN de `getMediaUris`, no la dirección IP directa.
  </Accordion>
</AccordionGroup>

## Próximos pasos

<CardGroup cols={2}>
  <Card title="React SDK" icon="react" href="/es/implementations/react-sdk">
    Reproductores LAN y WAN listos para usar que manejan los tokens federados por ti.
  </Card>

  <Card title="Detección en tiempo real por LAN" icon="vector-square" href="/es/implementations/lan-realtime-detection-overlay">
    Formato TLV completo de H.264 y superposiciones de cuadros delimitadores de IA.
  </Card>

  <Card title="Streaming de video" icon="video" href="/es/implementations/streaming-video">
    Miniaturas, streams compartidos y clips grabados a través de la nube.
  </Card>

  <Card title="Obtener audio" icon="microphone" href="/es/implementations/retrieving-audio">
    Streams y grabaciones de gateways de audio.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.