Cómo recibes la traducción

Ya sabes cómo entra el audio: una sala, y algo que emite dentro de ella. Esta página es la otra mitad — por dónde sale, que es lo que decide cuánto trabajo tienes tú.

Hay tres caminos y no compiten: uno es llave en mano, otro es para quien ya tiene su propia plataforma, y el tercero es para quien quiere montar la interfaz él, con su marca y dentro de su producto. Se puede usar cualquiera, o varios a la vez.


Camino 1 · Nuestra sala, llave en mano

Lo pones todo nosotros y tú te conectas. La sala existe ya —la creaste en el paso 3— y ahí dentro está todo lo que produce la traducción:

  • Una pista de audio por cada idioma, en directo. Si la sala traduce a tres, hay tres pistas sonando a la vez y cada oyente se suscribe a la suya.
  • El texto de lo que se va diciendo, por el canal de datos de la misma conexión: la transcripción de lo dicho y su traducción a cada idioma.

Para entrar sólo hacen falta dos cosas, que te damos nosotros:

Qué Qué es
La dirección de la sala A dónde se conecta quien escucha
Un pase temporal Le deja escuchar esa sala y caduca solo

Es WebRTC estándar, así que del otro lado vale cualquier cliente que hable WebRTC — incluido un navegador, sin instalar nada.

Cómo se distinguen los idiomas

Cada pista se llama <origen>_<destino>. En una sala que se habla en español y traduce a inglés y francés hay dos pistas: es_en y es_fr. Quien escucha se suscribe a una y ya está; cambiar de idioma es cambiar de pista, no reconectar.

El texto, y cuántos mensajes son

Los mensajes de texto viajan por el canal de datos con el tema liora.transcript. Filtrar por ese tema deja descartar lo que no es tuyo sin tener que abrir el JSON de cada frase; un cliente que no lo mire recibe lo mismo, porque el tema es metadato del paquete.

Cuántos son depende de si hay a quién traducir:

  • Traduciendo: cada frase produce el original y una traducción por idioma, todos con el mismo segment_id, para poder pintarlos en la misma línea en vez de ir apilando una por idioma.

⚠️Júntalas por `source_user` **y** `segment_id`, no sólo por el número

El segment_id se repite entre personas. Lo lleva cada micrófono por su cuenta y todos empiezan en 1, así que en una sala con dos personas hablando hay dos frases distintas con segment_id: 1.

Medido el 04/09/2026 con dos clientes de verdad: {1: [ana, bruno], 2: [ana, bruno]}. Juntando sólo por el número, a una frase le puedes pegar la traducción de otra persona — y eso no da ningún error: se lee, y dice otra cosa.

La pareja es (source_user, segment_id), y source_user viaja en los dos mensajes.

  • Subtitulando —o traduciendo pero con todos hablando el mismo idioma, que es lo mismo para esto—: un solo mensaje, el original_transcript. No hay a quién traducir, así que no hay traducción que mandar.

Esa segunda fila es nueva desde el 04/09/2026: antes, una sala de sólo subtítulos no producía texto por este canal.

{
  "type": "original_transcript",
  "original_text": "¿Empezamos?",
  "original_language": "es",
  "source_user": "u_9f2c",
  "display_name": "Ambón",
  "timestamp": "2026-09-04T18:30:12.481Z",
  "segment_id": 7
}
{
  "type": "translation_text",
  "text": "Shall we start?",
  "language": "en",
  "source_language": "es",
  "source_user": "u_9f2c",
  "display_name": "Ambón",
  "timestamp": "2026-09-04T18:30:12.481Z",
  "segment_id": 7
}

source_user es quién habla y es el campo que hay que usar para agrupar por persona: display_name es para enseñar y puede no venir. timestamp es ISO 8601.

💡Si tu cliente no conoce un tipo de mensaje, se calla

Un mensaje con un type que no esperas no da error: se ignora en silencio. Así que si no ves subtítulos, mira primero si estás filtrando por un type que ya no llega.


Camino 2 · A tu propia plataforma

Si ya tienes WebRTC —una plataforma de vídeo, una app de eventos, una sala de control— no tienes que entrar en ninguna sala nuestra: dinos tu endpoint WHIP y publicamos ahí.

  • Cada idioma es una sesión aparte. No una sesión con varias pistas: un extremo WHIP puede aceptar una sola pista de audio, y cinco idiomas en una llegarían como uno, con cuatro perdidos en silencio.
  • Si pones {lang} en la dirección, cada idioma va por su ruta; si no, el idioma viaja como parámetro lang.
  • Si nos das un token, se manda como Authorization: Bearer.
  • Al terminar una emisión cerramos la sesión, así que no queda nada abierto.
https://tu-plataforma.example/ingest/{lang}/whip   →  …/ingest/en/whip
https://tu-plataforma.example/whip                 →  …/whip?lang=en

Funciona con lo que ya usa todo el mundo para entrar audio por WHIP; si tu plataforma lo habla, esto entra.

El texto va por su lado

WHIP transporta medios y no lleva canal de datos, así que los subtítulos no caben ahí. Si los quieres, danos una dirección y te mandamos cada línea traducida por HTTP según sale.

Es un webhook y no una conexión abierta a propósito: es una línea cada pocos segundos, y así sobrevive a que reinicies tu servidor y pasa por cualquier proxy de empresa.

Qué te llega, campo a campo. Un POST con Content-Type: application/json por cada línea. Si nos das un token, va en Authorization: Bearer.

{
  "room": "rm_abc123",
  "segmentId": 12,
  "lang": "en",
  "sourceLang": "es",
  "text": "Good morning everyone",
  "sourceUser": "juan",
  "displayName": "Juan",
  "ts": 1788557146020
}
  • segmentId — el orden de captura de la frase que produjo esta línea. Todos los idiomas de una misma frase lo comparten, así que sirve para agruparlos… con el aviso de arriba: se repite entre personas, y la pareja buena es (sourceUser, segmentId).
  • sourceLang es lo que se habló y lang el idioma de text.
  • displayName puede venir vacío en las primeras frases, y no es un fallo: el nombre de un invitado se anuncia DESPUÉS de que empiece a hablar. Deja un hueco que se rellene, no un nombre que falta.
  • ts es cuándo lo mandamos, en milisegundos.

Son los mismos nombres que en el canal de datos para las mismas cosas, así que si lees los dos caminos no tienes que aprender dos vocabularios.


Camino 3 · Tu aplicación, dentro de nuestra sala

El más potente, y el que más control te da. Tu aplicación entra en la sala como un participante más: mete el micrófono de cada persona y se lleva la traducción, y la interfaz la haces tú — tu marca, tu producto, tu diseño.

No hay ninguna librería nuestra que instalar. Todo lo de aquí es WebRTC estándar, así que vale lo que ya sepas usar: el navegador lo trae de fábrica, y en móvil o escritorio cualquier librería de las de siempre.

Lo primero: un pase por persona

curl -X POST https://<nuestra-api>/api/v1/rooms/<sala>/token \
  -H "X-API-Key: TU_LLAVE" -H "Content-Type: application/json" \
  -d '{"identity":"juan","name":"Juan","can_publish":true,"language":"es"}'
{ "success": true, "token": "…", "url": "wss://…", "room": "…",
  "identity": "juan", "can_publish": true, "expires_at": 1787454262679 }

Uno por persona y por sesión. Lo pide tu servidor con tu llave y se lo pasa a tu aplicación; la llave no sale de tu servidor.

Campo Qué es
identity Quién es esa persona para ti. Es lo que verás en el texto
name Lo que se enseña. Opcional
can_publish true si esa persona habla; false si sólo escucha
language El idioma que habla. Obligatorio si publica
ttl_minutes Cuánto vale. Opcional, con tope

La identidad con la que entra la compone el servidor: quien sólo escucha entra marcado como oyente y quien habla entra sin marcar. Usa la que te devuelve en identity, no la que mandaste.

Una pista por persona, nunca la mezcla

Si mandas la mezcla de tu reunión, todo cae en la misma transcripción: no hay forma de saber quién habla ni desde qué idioma traducir. No falla nada — simplemente sale mal. Y por eso quien publica declara su idioma: sin él no hay nada que traducir, y la llamada al token te lo dirá en vez de dejarte descubrirlo el día del acto.

Hablar: publica el micrófono

Con ese pase, tu aplicación publica el micrófono de esa persona en la sala. Si publicas con tu propia librería en vez de con un cliente completo, manda el nivel de audio en la cabecera (la extensión estándar ssrc-audio-level): es lo que permite quedarse con quien habla cuando hay mucha gente. Si no llega, se escucha a todo el mundo — funciona igual, pero pagas por todos.

Escuchar: pide el idioma que quieras

Para llevarte una traducción a tu aplicación tienes dos formas y las dos son estándar:

  • Como un participante más: te suscribes dentro de la sala a la pista del idioma que quieras, y el texto llega por el canal de datos de esa misma conexión.

  • Con un POST y ya está, si prefieres no entrar en la sala: mandas tu oferta a /whep/<idioma> con tu pase en la cabecera Authorization, y contestamos con la respuesta. A partir de ahí suena. Para colgar, un DELETE a la dirección que te damos en Location.

    curl -X POST https://<nuestro-puente>/whep/en \
      -H "Authorization: Bearer <el pase de esa persona>" \
      -H "Content-Type: application/sdp" --data-binary @oferta.sdp
    

    La sala no se pide: sale del pase. Y si en esa sala todavía no hay nadie hablando ese idioma, contestamos 404 — vuelve a intentarlo, no hay nada roto.

El texto no cabe por ahí —ese protocolo lleva audio y no datos—, así que los subtítulos van por el canal de datos de la sala o por la dirección tuya del camino 2.

Cómo se llama cada cosa

Esto es lo único que hay que aprenderse, y son tres reglas:

Qué Cómo se llama
La pista que publica una persona La que tú quieras; quien es lo dice su pase
La pista de una traducción translator_<origen>_<destino>, por ejemplo translator_es_en
El tema de los mensajes de texto liora.transcript

Para escuchar en inglés, te suscribes a la pista cuyo nombre acaba en el idioma que quieres. El origen va delante para que sepas de qué se tradujo.

El texto: cuántos mensajes por frase

Los mensajes viajan con el tema liora.transcript, y cuántos son depende de si hay a quién traducir:

  • Traduciendo: el original y una traducción por idioma, todos con el mismo segment_id, para que los juntes en la misma línea en vez de ir añadiendo una por idioma.

⚠️Júntalas por `source_user` **y** `segment_id`, no sólo por el número

El segment_id se repite entre personas. Lo lleva cada micrófono por su cuenta y todos empiezan en 1, así que en una sala con dos personas hablando hay dos frases distintas con segment_id: 1.

Medido el 04/09/2026 con dos clientes de verdad: {1: [ana, bruno], 2: [ana, bruno]}. Juntando sólo por el número, a una frase le puedes pegar la traducción de otra persona — y eso no da ningún error: se lee, y dice otra cosa.

La pareja es (source_user, segment_id), y source_user viaja en los dos mensajes.

  • Subtitulando —o traduciendo con todos hablando el mismo idioma, que para esto es lo mismo—: un solo mensaje, el original_transcript. Nuevo desde el 04/09/2026; antes, una sala de sólo subtítulos no mandaba nada por aquí.

Esto es lo que más te importa de los tres caminos, porque el 3 es el único en el que te suscribes tú al canal: si tu pantalla espera siempre dos mensajes, se queda esperando el segundo en una sala donde todos hablan el mismo idioma.

Así se ven los dos, cuando hay traducción:

// Lo que se ha dicho
{ "type": "original_transcript", "original_text": "Buenos días a todos",
  "original_language": "es", "source_user": "juan", "display_name": "Juan",
  "timestamp": "2026-08-23T10:15:00Z", "segment_id": 12 }

// Y cada idioma al que se ha traducido
{ "type": "translation_text", "text": "Good morning everyone",
  "language": "en", "source_language": "es", "source_user": "juan",
  "display_name": "Juan", "timestamp": "2026-08-23T10:15:01Z", "segment_id": 12 }

source_user es la identidad con la que entró esa persona — la que te devolvió la llamada del pase, no la que mandaste.


Lo que te cuesta cada camino

Camino Trabajo tuyo Cuándo elegirlo
1 · Llave en mano Ninguno Quieres que funcione hoy
2 · A tu plataforma Un servidor que acepte audio y un endpoint para el texto Ya tienes por dónde escuchar
3 · Tu aplicación La interfaz, y publicar y suscribirte Quieres tu marca y tu producto

Los tres se pagan igual: por lo que se habla en la sala, no por camino.


Los dos a la vez

Publicar en tu plataforma no apaga la sala: por defecto es además, así que quien escuchaba por la sala sigue escuchando y tu sistema recibe lo mismo. Si sólo quieres lo tuyo, se puede dejar como único destino.


Quién configura esto

Tú, desde el panel o desde la API. En la ficha de cada sala hay una tarjeta, A dónde sale la traducción, con tres respuestas y ninguna que aprenderse: a una sala nuestra, a tu plataforma, o a las dos. Si eliges tu plataforma, pides la dirección y su clave, y hay un botón de probar ahora que llama de verdad a tu servidor antes de guardar nada y te dice qué contesta.

Por la API es lo mismo:

curl -X PUT https://<nuestra-api>/api/v1/rooms/<sala>/delivery \
  -H "X-API-Key: TU_LLAVE" -H "Content-Type: application/json" \
  -d '{"whip_url":"https://tu-plataforma/entrada","whip_only":false,"text_url":"https://tu-api/subtitulos"}'

Y POST …/delivery/test prueba una dirección sin guardarla.

ℹ️Se aplica en la próxima emisión

El reparto se monta al abrir la emisión, así que una sala que esté sonando ahora sigue saliendo donde salía hasta que termine. Lo dice también la pantalla.

La clave no vuelve nunca. Se dice si hay una puesta, y nada más. Guardar otros campos con el hueco de la clave en blanco no la borra: para quitarla hay que decirlo.

Lo demás —los idiomas de la sala, su nombre y si está encendida— lo manejas igual, desde el panel o desde la API.

ℹ️Los ajustes acústicos tampoco se tocan desde fuera

Cuánto tiene que sonar algo para contar como voz, o dónde acaba una frase, se afina escuchando cómo suena ese sitio. Si una sala no se oye como debería, dilo y la ajustamos.