Icecast + Python + Nginx: radio en tiempo real en la web
Conectar La MIX Radio con mi web en tiempo real fue uno de esos desafíos que, aunque parecían complejos al principio, se convirtieron en una oportunidad perfecta para aplicar nuestros conocimientos en el homelab. Como operador de radio AM/FM por más de 30 años y ahora al frente de radios online como La MIX Radio y Radio OnE, sabemos que la información actualizada en la web es crucial. No hay nada más frustrante para un oyente que visitar la página de su radio favorita y encontrar la programación o el tema actual desactualizado. En este artículo, veremos cómo logramos esta sincronización dinámica utilizando una combinación de Icecast2, Python, Nginx y la robusta infraestructura de nuestro Proxmox.
El problema o la necesidad de una conexión dinámica
La necesidad principal era clara: ¿cómo mostrar en la web de La MIX Radio (lamixradio.com) lo que está sonando en ese preciso instante? No solo el título de la canción, sino también el número de oyentes, el tipo de stream, y cualquier otra información relevante que Icecast2 nos ofrece. Si no resolvemos esto, la experiencia del usuario decae significativamente. Un oyente que llega a la web busca confirmación, busca interacción, quiere saber qué escucha y quizás, quién lo interpreta.
Sin una conexión en tiempo real, el contenido de la web se vuelve estático. Esto significa que si un oyente llega y ve información desactualizada, podría pensar que la radio no está activa o que su contenido no se gestiona con seriedad. En nuestro caso, operando radios 24/7 de forma independiente, cada detalle cuenta para retener a la audiencia y demostrar profesionalismo. Mantener la web actualizada manualmente es inviable. Necesitábamos una solución automatizada y fiable para conectar La MIX Radio con mi web en tiempo real.
Requisitos previos para nuestra integración
Antes de sumergirnos en el código, es fundamental tener nuestra infraestructura base funcionando. Esto es lo que nosotros utilizamos en nuestro homelab y que les recomendamos tener configurado:
- Un servidor Proxmox: La base de nuestro homelab. Sobre él, ejecutamos múltiples máquinas virtuales (VMs) y contenedores.
- Una VM o contenedor Debian/Ubuntu: Nosotros usamos nuestra VM 111 con Debian para ejecutar Icecast2 y el script de Python.
- Icecast2 instalado y funcionando: La MIX Radio usa Icecast2 para sus streams. Asegúrense de que el stream esté emitiendo correctamente.
- Nginx instalado y configurado: Actúa como servidor web y proxy inverso para nuestro WordPress y otras aplicaciones.
- Python 3: Con la librería
requestsinstalada. Pueden instalarla conpip install requests. - Conocimientos básicos de Bash y cron: Para automatizar la ejecución del script Python.
Con estos componentes listos, tenemos el terreno preparado para empezar a conectar La MIX Radio con mi web en tiempo real.
Solución paso a paso: arquitectura y código
La solución que implementamos se basa en un flujo sencillo pero eficaz: un script de Python consulta la API de Icecast2, procesa la información y la guarda en un archivo JSON. Luego, Nginx sirve ese archivo para que cualquier frontend web pueda consumirlo. Finalmente, un servicio systemd automatiza la ejecución del script.
Paso 1: Obtener los datos de Icecast2
Icecast2 expone su estado a través del endpoint /status-json.xsl, que devuelve un JSON con información de todos los mount points: título de la canción actual, número de oyentes y más. Asumiendo que Icecast2 escucha en el puerto 8000, la URL sería http://localhost:8000/status-json.xsl.
Paso 2: Procesar los datos con Python
Guardamos el siguiente script como icecast_scraper.py en /home/lamix/scripts/:
import requests
import json
import os
import datetime
# Configuración
ICECAST_STATUS_URL = "http://localhost:8000/status-json.xsl"
OUTPUT_DIR = "/home/lamix/"
OUTPUT_FILENAME = "nowplaying.json"
def get_icecast_data():
"""Obtiene los datos JSON del estado de Icecast2."""
try:
response = requests.get(ICECAST_STATUS_URL, timeout=5)
response.raise_for_status()
return response.json()
except requests.exceptions.RequestException as e:
print(f"Error al obtener datos de Icecast: {e}")
return None
def process_icecast_data(data):
"""Procesa los datos de Icecast y extrae información relevante."""
processed_data = {"radios": []}
if not data or "icestats" not in data or "source" not in data["icestats"]:
print("Datos de Icecast no válidos o vacíos.")
return processed_data
sources = data["icestats"]["source"]
# Si solo hay un stream, 'source' no es una lista
if not isinstance(sources, list):
sources = [sources]
for source in sources:
if "/lamix.mp3" in source.get("listenurl", ""):
title = source.get("title", "Desconocido")
artist = source.get("artist", "Desconocido")
# Unir artist y title si están separados
current_song = title
if artist and artist != "Desconocido" and artist not in title:
current_song = f"{artist} - {title}"
radio_info = {
"mount": source.get("mount", "N/A"),
"title": title,
"artist": artist,
"current_song": current_song,
"listeners": source.get("listeners", 0),
"genre": source.get("genre", "N/A"),
"bitrate": source.get("bitrate", "N/A"),
"server_description": source.get("server_description", "N/A"),
"listen_url": source.get("listenurl", "N/A"),
"updated_at": datetime.datetime.now().isoformat()
}
processed_data["radios"].append(radio_info)
break # Solo nos interesa el stream principal
return processed_data
def save_to_json_file(data, output_path):
"""Guarda los datos procesados en un archivo JSON."""
try:
with open(output_path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=4)
print(f"Datos guardados en {output_path}")
except IOError as e:
print(f"Error al guardar el archivo JSON: {e}")
if __name__ == "__main__":
icecast_data = get_icecast_data()
if icecast_data:
processed_data = process_icecast_data(icecast_data)
output_file_path = os.path.join(OUTPUT_DIR, OUTPUT_FILENAME)
save_to_json_file(processed_data, output_file_path)
else:
print("No se pudieron obtener datos de Icecast.")
Paso 3: Servir el archivo JSON con Nginx
Agregamos esta configuración en /etc/nginx/sites-available/lamixradio.com:
server {
listen 80;
listen [::]:80;
server_name lamixradio.com www.lamixradio.com;
location = /nowplaying.json {
root /home/lamix;
expires 1s;
add_header Access-Control-Allow-Origin *;
add_header Cache-Control "no-store, no-cache, must-revalidate, proxy-revalidate, max-age=0";
}
# Resto de la configuración...
}
Verificamos y recargamos Nginx:
sudo nginx -t
sudo systemctl reload nginx
Paso 4: Automatizar la ejecución con systemd
La forma más robusta de mantener el scraper corriendo es como servicio systemd. Creamos el archivo /etc/systemd/system/icecast-scraper.service:
[Unit]
Description=Icecast Now Playing Scraper - La MIX Radio
After=network.target
[Service]
User=lamix
WorkingDirectory=/home/lamix/scripts
ExecStart=/usr/bin/python3 -u icecast_scraper.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Activamos y arrancamos el servicio:
sudo systemctl daemon-reload
sudo systemctl enable icecast-scraper
sudo systemctl start icecast-scraper
sudo systemctl status icecast-scraper
Paso 5: Integrar en la web (Frontend)
Con el archivo nowplaying.json disponible en https://lamixradio.com/nowplaying.json, el frontend lo consume con JavaScript y actualiza la página cada 5 segundos:
<!-- Elementos HTML en su página -->
<p>Ahora suena: <span id="current-song-title">Cargando...</span></p>
<p><span id="listener-count"></span></p>
<script>
function updateNowPlaying() {
fetch('https://lamixradio.com/nowplaying.json?_=' + new Date().getTime())
.then(response => response.json())
.then(data => {
if (data.radios && data.radios.length > 0) {
const radio = data.radios[0];
document.getElementById('current-song-title').innerText = radio.current_song;
document.getElementById('listener-count').innerText = 'Oyentes: ' + radio.listeners;
}
})
.catch(error => console.error('Error al obtener nowplaying.json:', error));
}
updateNowPlaying();
setInterval(updateNowPlaying, 5000);
</script>
La clave para una integración exitosa al conectar La MIX Radio con mi web en tiempo real es la automatización robusta del scraper y la configuración correcta de Nginx para entregar el JSON sin caché.
Errores comunes y cómo evitarlos
En nuestra trayectoria de 30 años en radio y como autodidactas en sistemas, hemos tropezado con estos errores recurrentes:
- Permisos del archivo JSON: El script Python genera el
nowplaying.jsoncon permisos que Nginx no puede leer. Solución:chmod 644 /home/lamix/nowplaying.jsonychmod 755 /home/lamix/. El usuario que ejecuta el script debe tener permisos de escritura en el directorio. - URL de Icecast incorrecta: Si el script no obtiene datos, verificar la URL directamente en el navegador:
http://localhost:8000/status-json.xsl. Los logs del servicio systemd son clave:journalctl -u icecast-scraper -f. - Caché agresiva de Nginx o el navegador: Las directivas
expires 1syCache-Controlen Nginx son fundamentales. En el frontend, agregamos el parámetro?_=timestampa la URL del fetch para forzar la revalidación.
Resultado final y verificación
Con todo configurado, la verificación es directa. Primero, comprobamos que el archivo se actualiza en el servidor:
watch -n 2 cat /home/lamix/nowplaying.json
El campo updated_at debe cambiar cada pocos segundos. Luego, accedemos a https://lamixradio.com/nowplaying.json desde el navegador. Si vemos el JSON actualizado al recargar, Nginx está sirviendo correctamente. Finalmente, abrimos lamixradio.com y cambiamos una canción desde Audacious. En menos de 10 segundos, el título debe actualizarse en la web sin recargar la página.
Conclusión y siguiente paso
Hemos recorrido el camino completo para conectar La MIX Radio con mi web en tiempo real, desde la obtención de datos de Icecast2 hasta su visualización dinámica. Esta arquitectura basada en Python, Nginx e Icecast2 sobre nuestro homelab Proxmox demuestra que es posible operar una radio online con experiencia profesional usando recursos independientes.
El siguiente paso natural es extender el script Python para enviar estos datos a n8n, lo que nos permitiría automatizar publicaciones en redes sociales cada vez que cambia una canción. También podríamos agregar un historial de las últimas canciones emitidas para enriquecer aún más la experiencia en la web.

