// Open TV · Cómo está hecha la demo

Transmite de verdad. Lo caro es que sea honesta.

La demo de este sitio reproduce 54 canales de 32 emisoras que publican su señal en abierto, en directo, y korven.dev no toca un solo byte de ese vídeo. Sostener esas dos frases a la vez obligó a tres decisiones: una regla de admisión que descartó a la mayoría de los candidatos, un navegador que habla directo con cada emisora, y un catálogo que se corrige solo. Esta página las explica para quien quiera leer el sistema además de mirarlo.

// La regla de admisión

Solo la infraestructura del propio emisor.

Un canal entra si su manifiesto vive en un host del emisor —no en un revendedor, no en un espejo— y si toda la cadena se reproduce desde otro origen: el manifiesto, la variante que elige el reproductor y el primer segmento de vídeo, cada uno con su permiso CORS. Un manifiesto con CORS no basta: si los segmentos no lo traen, el vídeo arranca, se ve un instante y muere a los dos segundos, y la demo habría dicho que el canal funciona. Por eso cada comprobación baja unos kilobytes de vídeo real.

Esa regla costó el catálogo: de 193 candidatos quedaron 54. Se cayeron Las Estrellas, bitMe, MVS y la cola larga de revendedores mexicanos, porque ninguno estaba en un host oficial. Es la decisión que mantiene la demo en blanco, y no se negocia por un canal más.

$ opentv:snapshot— manifiesto → variante → segmento, con Origin: korven.dev

rtve.es · 24 Horas

✓ manifiesto ✓ variante ✓ segmento (2 KB) · CORS en los tres · entra

cdn-intermediario.example · «Canal X»

✕ el host no es del emisor · no entra, aunque reproduzca

emisora.example · «Canal Y»

✕ el manifiesto permite otro origen; el segmento no · no entra

// Cero bytes por korven.dev

El navegador habla con la emisora. Este sitio no está en medio.

Las dos páginas que reproducen vídeo se abren su propia política de seguridad —connect-src, media-src y el worker-src blob: sin el que el reproductor muere sin un solo mensaje— y una prueba comprueba que ninguna otra página del sitio la tiene. El reproductor HLS se descarga solo cuando alguien elige un canal, en un paquete aparte, para que nadie lo pague por entrar a la página de venta.

Elegir la vía de reproducción parece trivial y no lo es. Preguntarle al navegador si sabe HLS de forma nativa miente: Chrome de escritorio contesta «quizá» y luego no sabe desmultiplexar el stream; el vídeo se queda pausado y en silencio, sin un error que capturar. El único indicador fiable es el contrario: donde no hay MediaSource —iOS— la vía nativa es la única y además funciona. Así que hls.js siempre que se pueda, nativo solo donde no.

// Se cura sola

Una instantánea que es el suelo, y un cron que solo corrige.

Las señales de televisión abierta se mueren y cambian de dirección sin avisar. Nada de esto reescribe el catálogo que se publica —eso es una decisión humana—: se guardan correcciones, y la demo nunca queda peor de lo que trae dentro.

01

La instantánea es el suelo

El catálogo viaja dentro de la página: 54 canales verificados a mano, . Funciona sin red, sin base de datos y sin que ningún servidor conteste. No se regenera al construir el sitio, a propósito: la construcción tiene que ser determinista y no depender de que una emisora esté viva a esa hora.

02

La misma sonda, dos veces al día

Un cron recorre cada canal con exactamente la misma comprobación que usa el generador manual: manifiesto, variante y primer segmento. Si cada uno tuviera su copia, acabarían divergiendo, y el día que divergen el cron admite lo que el generador habría rechazado — en silencio y solo en producción.

03

La guía para curar viaja con la fila

Cuando una URL muere, el cron busca otra del mismo canal en el índice público de iptv-org, pero solo la adopta si el host sigue siendo del emisor y la URL contiene la marca que fija la señal concreta. Sin eso, «curar» France 24 Español podía dejarlo en la edición en árabe: comparten identificador.

04

Muere a la tercera, y solo si el fallo es concluyente

Un canal sale del catálogo tras 3 fallas comprobadas sin una señal buena de por medio, y solo cuentan los fallos que significan lo mismo desde cualquier sitio: el recurso ya no existe, dejó de ser HLS, se cifró. Un 403, un 451, un 5xx o un tiempo agotado se registran, pero nunca matan. Importa: el cron corre en Estados Unidos y las 12 emisoras mexicanas bloquean por región. Sin esa distinción las habría borrado todas en un día y medio.

05

Asciende desde una banca ya vetada

Un catálogo que solo puede encogerse acaba vacío. Hay 16 suplentes que pasaron la misma admisión y esperan en orden de preferencia; por cada titular muerto sube el primero que reproduzca hoy — no el primero de la lista, porque la reserva también se pudre.

06

Solo viajan las diferencias

La página no vuelve a pedir el catálogo: pide las correcciones —una URL que cambió, un canal caído, uno ascendido— y las funde sobre lo que ya trae. Si la tabla o el endpoint desaparecen, la demo se comporta exactamente como si nunca hubieran existido. Nunca peor que la instantánea.

// El televisor

Dos tubos, y un corte de 560 milisegundos.

HLS tarda entre dos y seis segundos en dar el primer cuadro. Un solo <video> significaba una pantalla negra en cada cambio de canal. El aparato lleva dos: mientras uno se ve, en el otro se carga el canal siguiente, y el intercambio ocurre tapado por la interferencia. Se alternan por opacidad, nunca ocultando el elemento: un vídeo oculto no se decodifica, y el segundo tubo no habría precargado nada. Se midió: con la versión anterior, uno de cada cuatro cambios llegaba con imagen.

La política de la rotación es una función pura, sin reloj propio y con pruebas: cada canal se queda 18 segundos, el siguiente empieza a cargarse 4 segundos antes del salto, y al entrar el aparato sintoniza 2 tarjetas antes de enganchar la primera señal. La interferencia también tiene reglas para leerse como una falla y no como una animación: cae en más o menos uno de cada 4 saltos, nunca en dos seguidos, y se fuerza tras 6 saltos limpios para que no desaparezca en una mala racha.

No lleva controles nativos: sobre una señal en vivo Chrome pinta una barra de avance que no significa nada. Sí lleva un botón de PAUSA que detiene el vídeo y la rotación, con la etiqueta dentro para que el nombre accesible sea el mismo que se lee. Y cuando un canal se cae, se enseña: el corte, el cartel, de quién es la culpa. Fingir «reintentando…» para siempre sí sería un fallo de la demo.

// Lo que no se construyó, a propósito

  • Ningún proxy. La app instalada reintenta un canal difícil por un proxy en loopback; un sitio web no puede hacerlo sin retransmitir vídeo ajeno, que es la línea que Open TV no cruza. Un canal que el navegador no reproduce solo no entra.
  • Ningún host de revendedor, ninguna cabecera falsificada, ningún stream cifrado. La admisión es lo que mantiene la demo en blanco: si un canal solo funciona fingiendo ser otro navegador o pasando por un intermediario, no es televisión abierta.
  • Ningún logotipo. Son marcas de terceros, y traerlos significaría pedirle imágenes a servidores ajenos desde korven.dev. Las iniciales se dibujan con los tokens de la casa, así que la política de imágenes del sitio nunca se abre a nadie.
  • Ninguna cuenta y ninguna telemetría. La tabla de señales es del servidor y niega todo acceso que no sea el suyo; el navegador de quien mira solo pide un JSON de correcciones y luego habla con las emisoras.

// Stack

Astro 5 y TypeScript, prerenderizado; hls.js en un paquete perezoso; política de seguridad por página con hashes y sin unsafe-inline. La tabla de señales vive en Supabase con la seguridad por filas encendida y cero políticas —solo la llave del servidor entra— y el cron corre en Vercel. Las piezas que deciden —catálogo, sintonizador, rotación, fusión, sonda— son módulos puros con pruebas en vitest; lo que solo existe pintado —que el televisor quepa en un teléfono, que buscar esconda filas, que un canal caído se anuncie— lo cubre Playwright en un navegador real, con la red a las emisoras cortada para que no dependa de nadie. La app que se instala es otro repositorio: Go y Svelte, licencia MIT.