Controller y Player en una sola app Flutter: un modo por dispositivo
En señalización digital hay dos tipos de aparato. Uno es la pantalla: una tele en un lobby, una tablet en un mostrador, un monitor en una vitrina. Solo reproduce y casi nadie la toca. El otro es desde donde se manda: el teléfono o el portátil de quien sube contenido, arma playlists y decide qué suena a qué hora. En ChronoFlow llamamos al primero Player y al segundo Controller, y los dos son la misma app Flutter.
Este artículo cuenta cómo una sola compilación hace de consola de gestión en un teléfono y de pantalla en una tele: dónde se decide el modo, qué cambia en cada uno y qué descartamos por el camino.
Un modo por dispositivo, no una compilación por modo
La app se publica como un único paquete para teléfonos y tablets Android, Android TV e iOS. El manifiesto de Android declara la interfaz de TV como opcional, así que el mismo APK aparece en el lanzador de la tele y en el de un teléfono. Lo que distingue un Controller de un Player no es el binario sino un valor guardado en el almacenamiento seguro del dispositivo: el modo de funcionamiento.
Ese valor se fija la primera vez y se respeta en cada arranque:
- En Android TV la app arranca como Player por defecto. Nadie quiere administrar playlists con un mando a distancia, y una tele que se enciende por la mañana debe ir directa a reproducir.
- En teléfonos y tablets la pantalla de entrada tiene dos pestañas: iniciar sesión, que lleva al Controller, y «Usar como Player», que empareja el aparato sin que nadie escriba una contraseña. Una tablet vieja puede convertirse en pantalla en un minuto.
Para saber si está en una tele, la app pregunta a Android una sola vez, al arrancar, por las señales oficiales de ese tipo de dispositivo. No lo deduce del tamaño de pantalla: una tablet grande en horizontal no es una tele, y una tele no deja de serlo porque la resolución sea baja.
Un único punto donde se bifurca
El enrutador de la app solo sabe si hay una sesión o no. La bifurcación ocurre en un solo sitio: la pantalla de inicio mira el modo y monta una interfaz u otra. La interfaz del Player no incluye ninguna navegación del Controller, ni siquiera oculta. Así, una pantalla en un lugar público no tiene forma de llegar a la biblioteca de contenido ni a los ajustes de la cuenta, por mucho que alguien pulse botones del mando.
El código sigue la misma frontera. Cada funcionalidad vive en su carpeta y está claro para quién es: contenido, playlists, horarios y suscripción son del Controller; reproducción y caché sin conexión son del Player; el emparejamiento y los modelos de pantalla los usan los dos. Por debajo hay un núcleo común: cliente HTTP, almacenamiento seguro, tema y avisos en tiempo real.
Qué hace cada modo
El Controller inicia sesión con la cuenta y organiza el trabajo en cinco secciones: Inicio, Biblioteca, Horarios, Players y Más. Desde ahí se reclaman los códigos de pantallas nuevas (lo contamos en emparejar pantallas con un código y un QR) y se envían órdenes a pantallas o grupos: reproducir, pausar, detener, saltar o reiniciar. Un Controller nunca se registra solo como pantalla: si lo hiciera, cada teléfono que abre la app ocuparía uno de los huecos de pantalla del plan.
El Player se empareja con un código, fija la orientación horizontal y, en Android TV, pasa a modo quiosco a pantalla completa, sin barras del sistema. Se maneja con el mando: el D-pad y las teclas multimedia tienen la misma correspondencia que en el Player web, así que un técnico que ya conoce una tele Samsung no tiene que aprender otra cosa en una Android TV.
Los anuncios siguen la misma lógica de modos. En el plan gratuito, el Controller muestra una franja publicitaria y el Player puede intercalar anuncios entre contenidos, además de una marca de agua discreta. En Android TV no se muestra ningún anuncio de la app, ni siquiera en el plan gratuito.
Cómo sabe el Player qué reproducir
El Player no decide qué toca: se lo pregunta a la API. Consulta qué está programado ahora cada 20 segundos y si hay órdenes pendientes cada 4, pero casi siempre se entera antes: cuando algo cambia, la API le manda un aviso por WebSocket y, en paralelo, una notificación push. Ese aviso es un timbre, no el contenido: no dice qué reproducir, solo que conviene volver a preguntar. La respuesta de la API es siempre la fuente de verdad, y si el aviso se pierde, la siguiente consulta lo arregla. Las órdenes remotas se confirman: el Player responde con su estado de reproducción y el Controller sabe si la orden llegó.
Samsung y LG: el mismo papel, otra implementación
Las teles Samsung (Tizen) y LG (webOS) no ejecutan Flutter. Para ellas el Player es una aplicación web en Vue, la misma base de código que el dashboard, empaquetada como app de tele. Hace el mismo papel que el Player Flutter y habla con la misma API, pero es otra implementación: los intervalos de consulta, el mapa de teclas del mando y la forma de detectar un cambio de contenido se mantienen iguales a mano. Qué supone correr en el motor web de una tele lo contamos en CORS en señalización.
Lo que descartamos
- Dos apps en la tienda. Duplica fichas, revisiones y versiones, y obliga a quien instala a saber cuál bajar. Con una sola app, cualquier Android puede ser pantalla sin buscar nada más.
- Un sabor de compilación por modo. Una tablet que hoy es Controller no podría pasar a Player sin reinstalar. El modo en tiempo de ejecución lo cambia en segundos.
- Decidir por tamaño de pantalla. Confunde tablets grandes con teles y teles de baja resolución con tablets. Las señales del sistema no se equivocan.
- Esconder las pantallas del Controller dentro del Player. Un menú oculto sigue siendo alcanzable. Si la interfaz del Player no lo contiene, no hay nada que proteger.
Lo que haríamos distinto
Mantener dos Players, Flutter y Vue, en paridad manual funciona, pero cada cambio de comportamiento hay que hacerlo dos veces y recordar hacerlo. Si empezáramos hoy, escribiríamos antes el contrato del Player (intervalos, teclas, cuándo cambiar de contenido) como una especificación compartida con pruebas en los dos lados, en lugar de descubrir las diferencias probando en la tele.
Para seguir leyendo
Cómo se une una pantalla a la cuenta: emparejar pantallas con un código y un QR. Cómo decide la API qué toca: playlists, horarios y grupos. El producto: ChronoFlow. Si tu producto también tiene que funcionar en teléfonos y teles: Servicios.