Playlists, horarios y grupos: cómo decide una API de señalización qué se reproduce ahora
El centro de cualquier sistema de señalización es una pregunta que cada pantalla hace una y otra vez: ¿qué tengo que reproducir ahora? En ChronoFlow la responde la API, a partir de cuatro piezas: archivos, playlists, horarios y grupos de pantallas. Este artículo cuenta cómo las modelamos, cómo se decide el ganador cuando varios horarios coinciden y cómo se entera la pantalla de un cambio en uno o dos segundos.
Archivos y playlists
Un archivo es una imagen, un vídeo o un enlace de YouTube de la biblioteca de la cuenta. Una playlist es una lista ordenada de archivos con dos valores por defecto: cuánto dura cada imagen (10 segundos si nadie dice otra cosa) y si los vídeos suenan o van en silencio. Por defecto van en silencio, porque muchas teles solo reproducen vídeo automáticamente si está silenciado, y en un lobby el sonido no siempre es bienvenido.
Cada elemento de la playlist guarda su posición, y puede tener su propia duración, su propia transición (ninguna, fundido, deslizamiento o zoom) y su propio ajuste de sonido. Así, la misma imagen puede durar 5 segundos en una playlist y 30 en otra sin duplicar el archivo.
Horarios y grupos
Un horario asigna una playlist a un destino durante un periodo. El destino puede ser una pantalla o un grupo de pantallas: programar «la carta de la mañana» para el grupo «Cafeterías» la lleva a todas las pantallas del grupo, incluidas las que se añadan después.
Hay varios tipos: franja horaria (de tal hora a tal hora, con repetición diaria o semanal), fechas de calendario, días de la semana y reglas de recurrencia. El plan gratuito usa solo franjas horarias y una playlist activa por pantalla; Pro tiene todos los tipos. Cada horario tiene además una prioridad de 0 a 500 para desempatar con otros del mismo origen.
Todas las horas se guardan en UTC y los clientes las convierten a la hora local al mostrarlas y al editarlas. Las franjas que cruzan la medianoche, como de 22:00 a 02:00, están soportadas.
Qué toca ahora
Cuando una pantalla pregunta qué reproducir, la API sigue siempre los mismos pasos:
- Candidatos. Los horarios dirigidos a esa pantalla y los dirigidos a cualquiera de sus grupos.
- Filtro. Fuera los pausados y los que no están activos en este momento.
- Puntuación. Cada candidato suma un peso según su origen y su prioridad. Una emergencia pesa 20000; una orden manual, 10000; un horario normal, 0. Como la prioridad no pasa de 500, ningún horario normal puede tapar una orden manual, y ninguna orden manual puede tapar una emergencia.
- Respuesta. Gana la puntuación más alta. Si no hay ninguno activo, suena el loop de playlist de la pantalla. Si tampoco hay loop, la respuesta es que no hay nada programado y la pantalla lo dice.
El loop es de la pantalla, no un horario
La forma obvia de ofrecer «reproducir esta playlist en bucle» era crear un horario más. La descartamos: ese horario llenaría la lista de programación, competiría con los horarios reales y obligaría a decidir qué prioridad tiene. En su lugar, el loop es un estado de la pantalla: cada pantalla puede tener una playlist de fondo que suena cuando no gana ningún horario. La regla queda simple: los horarios mandan y el loop rellena los huecos. El plan gratuito permite una pantalla en loop y Pro, veinte.
Pausar en lugar de borrar
Si una cuenta vuelve al plan gratuito y tiene más horarios o pantallas de los que permite, los que sobran se pausan con un motivo, no se borran. El paso de filtro los ignora, y volver a Pro los reactiva tal como estaban. Lo mismo vale para el loop: una pantalla puede tener su loop pausado sin perderlo.
Cómo se entera la pantalla de un cambio
Cuando se guarda un horario, la API encola un aviso para las pantallas afectadas y lo envía por dos caminos a la vez: un WebSocket privado de la pantalla y una notificación push. El aviso no lleva contenido, solo dice «vuelve a preguntar». La pantalla pregunta y compara la respuesta con lo que está reproduciendo mediante una identidad formada por el origen, el horario y la playlist. Solo cambia si la identidad es distinta, así que un aviso repetido no reinicia nada.
La identidad tiene tres partes por una razón: comparar solo el horario no bastaba, porque cuando no hay horario activo ese valor es nulo tanto si la pantalla está en su loop como si no tiene nada, y la pantalla no notaba el paso de uno a otro.
Con el WebSocket conectado, un cambio llega a la pantalla en uno o dos segundos. Si el aviso se pierde, la pantalla pregunta igualmente cada 20 segundos, así que nunca se queda desfasada más que eso.
Órdenes remotas
Además de los horarios, el Controller puede mandar órdenes a una pantalla o a un grupo: reproducir, pausar, detener, saltar o reiniciar. Cada orden pasa por estados (aceptada, entregada, confirmada o fallida) y la pantalla la confirma con su estado de reproducción. Cuando la orden va a un grupo, las pantallas que llevan más de un minuto sin dar señales se saltan y se informan como omitidas, en lugar de dejar la orden esperando para siempre.
Dónde vive cada cosa
La API sigue una arquitectura por capas estricta: los controladores solo orquestan la petición, los servicios contienen las reglas (la puntuación, los límites del plan, la pausa por bajada de plan) y los repositorios son los únicos que tocan la base de datos, siempre filtrando por el dueño. Una pantalla nunca ve horarios de otra cuenta porque la consulta ni siquiera puede pedirlos.
Lo que haríamos distinto
Guardar todo en UTC simplifica comparar y ordenar, pero una franja de 9:00 a 17:00 definida en invierno se desplaza una hora cuando cambia el horario de verano en los países que lo tienen. Si lo diseñáramos hoy, guardaríamos las franjas recurrentes como hora local más zona horaria y convertiríamos a UTC al evaluarlas, que es lo que la persona que programa tiene en la cabeza.
Para seguir leyendo
Quién hace las preguntas: Controller y Player en una sola app. Dónde viven los archivos: almacenamiento de objetos por plan. El producto: ChronoFlow. Si estás modelando programación o rotación de contenido: Servicios.