Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
KiraStream
Servidor de gestión de líneas IPTV compatible con el protocolo Xtream Codes. Permite administrar proveedores, usuarios, catálogos y monitorización en tiempo real desde un panel web, sirviendo los streams a cualquier reproductor IPTV estándar.
Índice
- Características
- Arquitectura
- Requisitos
- Instalación
- Configuración
- Estructura del proyecto
- API
- Panel de administración
- Gestión del servicio
- Registro y monitorización
- Notas técnicas de estabilidad
Características
- Proxy de streams en tiempo real — reencaminamiento de canales en directo mediante ffmpeg en modo copy (sin recodificación). Fan-out a múltiples clientes sobre una única conexión upstream.
- API compatible con Xtream Codes — funciona con IPTV Smarters, TiviMate, Perfect Player, VLC, Kodi y cualquier reproductor que soporte el protocolo Xtream.
- Gestión de proveedores — múltiples cuentas, múltiples dominios/CDN por cuenta con failover automático, sincronización de catálogos y health check cada 5 minutos.
- Gestión de usuarios — usuarios independientes con límite de conexiones simultáneas, prioridad, fecha de expiración y catálogos personalizados.
- EPG integrado — fusión de múltiples fuentes XMLTV, filtrado por canal y servicio en
/xmltv.php. - VOD y Series — proxy transparente para películas y series con soporte de
Range(seek). - Integración Jellyfin — biblioteca local de películas y series desde un servidor Jellyfin.
- Monitorización en tiempo real — WebSocket con métricas de streams, clientes, CPU de ffmpeg, salud A/V, buffer y reconexiones.
- Registro de fallos — eventos de caída y recuperación de streams persistidos en base de datos y visibles en el panel.
- Reconexión transparente — al caer un proveedor el buffer de cola del servidor absorbe el tiempo de reconexión; los clientes no ven pantalla negra.
Arquitectura
Cliente IPTV (TV/móvil/PC)
│ HTTP TS stream
▼
Nginx :80
│ proxy_pass
▼
FastAPI / Uvicorn :8000
│
├── ProviderPool (pool.py)
│ └── BroadcastGroup × canal activo
│ └── ffmpeg (copy mode) ← proveedor IPTV
│
├── VodTracker (vod_tracker.py) ← películas / series
├── EPGManager (epg_manager.py) ← XMLTV fusion
└── HealthChecker ← ping proveedores c/5min
PostgreSQL 17 ←→ SQLAlchemy async (asyncpg)
Redis ←→ pub/sub monitorización + slots de proveedor
Flujo de un canal en directo
- El reproductor solicita
GET /{user}/{pass}/{stream_id}. ProviderPool.acquire()busca o crea unBroadcastGrouppara ese canal.- El grupo lanza un proceso ffmpeg que lee del proveedor y escribe MPEG-TS en stdout.
- Cada chunk (64 KB) se despacha a las colas asyncio de todos los clientes conectados.
- El
StreamingResponsede FastAPI lee de la cola y lo envía al reproductor via HTTP chunked. - Cuando el proveedor cierra la sesión (~2 min), ffmpeg muere limpiamente y el loop de Python lo reinicia con una conexión fresca. El buffer de cola cubre el tiempo de reconexión.
Requisitos
| Componente | Versión mínima |
|---|---|
| Python | 3.11+ (probado en 3.13) |
| PostgreSQL | 14+ (probado en 17) |
| Redis | 6+ |
| Nginx | 1.18+ |
| ffmpeg | 5+ (probado en 7.1) |
| Node.js | 18+ (solo para compilar el frontend) |
Instalación
1. Dependencias del sistema
apt install -y python3 python3-venv python3-pip \
postgresql redis-server nginx ffmpeg \
nodejs npm
2. Clonar el repositorio
git clone https://git.kiracloud.es/joaquin/KiraTV.git /opt/kirastream
cd /opt/kirastream
3. Entorno Python
python3 -m venv venv
source venv/bin/activate
pip install fastapi uvicorn[standard] sqlalchemy[asyncio] asyncpg \
aiohttp redis pydantic-settings alembic python-jose \
passlib python-multipart
4. Base de datos
# Crear usuario y base de datos en PostgreSQL
su - postgres -c "createuser kirastream"
su - postgres -c "createdb -O kirastream kirastream"
su - postgres -c "psql -c \"ALTER USER kirastream PASSWORD 'kirastream_secret_2024';\""
Las tablas se crean automáticamente al arrancar la aplicación (Base.metadata.create_all).
5. Variables de entorno
Crear /opt/kirastream/.env:
DATABASE_URL=postgresql+asyncpg://kirastream:kirastream_secret_2024@localhost/kirastream
REDIS_URL=redis://localhost:6379/0
SECRET_KEY=cambia-esto-por-una-clave-segura
ADMIN_USERNAME=admin
ADMIN_PASSWORD=cambia-esto
LOG_LEVEL=INFO
6. Compilar el frontend
cd /opt/kirastream/frontend
npm install
npm run build
# Los archivos estáticos se generan en backend/static/
7. Nginx
Copiar la configuración al directorio de Nginx:
cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.bak
Contenido de /etc/nginx/sites-enabled/default:
upstream kirastream_backend {
server 127.0.0.1:8000;
keepalive 32;
}
server {
listen 80 default_server;
server_name _;
client_max_body_size 20m;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
proxy_connect_timeout 10s;
location /panel/ {
alias /opt/kirastream/backend/static/;
try_files $uri $uri/ /panel/index.html;
add_header Cache-Control no-cache;
}
location = / { return 302 /panel/; }
location / {
proxy_pass http://kirastream_backend;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection upgrade;
proxy_buffering off;
proxy_cache off;
add_header X-Accel-Buffering no always;
}
location /panel/assets/ {
alias /opt/kirastream/backend/static/assets/;
expires 30d;
add_header Cache-Control 'public, immutable';
}
gzip on;
gzip_types application/json application/javascript text/css text/plain application/x-mpegurl;
}
nginx -t && systemctl reload nginx
8. Servicio systemd
Crear /etc/systemd/system/kirastream.service:
[Unit]
Description=KiraStream IPTV Server
After=network.target postgresql.service
[Service]
Type=simple
User=root
WorkingDirectory=/opt/kirastream
ExecStartPre=/bin/bash -c 'pg_ctlcluster 17 main start 2>/dev/null; true'
ExecStartPre=/bin/bash -c 'redis-cli ping 2>/dev/null || (LANG=C LC_ALL=C redis-server --port 6379 --bind 127.0.0.1 --daemonize yes --dir /var/lib/redis/ && sleep 1); true'
ExecStartPre=/bin/bash -c 'pkill -9 uvicorn 2>/dev/null; sleep 1; true'
ExecStart=/opt/kirastream/venv/bin/uvicorn backend.main:app \
--host 0.0.0.0 --port 8000 --workers 1 --log-level info
StandardOutput=append:/opt/kirastream/logs/backend.log
StandardError=append:/opt/kirastream/logs/backend.log
Restart=always
RestartSec=5
KillMode=control-group
[Install]
WantedBy=multi-user.target
mkdir -p /opt/kirastream/logs
systemctl daemon-reload
systemctl enable --now kirastream
Configuración
Todas las variables se leen de /opt/kirastream/.env (o variables de entorno del sistema):
| Variable | Por defecto | Descripción |
|---|---|---|
DATABASE_URL |
postgresql+asyncpg://kirastream:...@localhost/kirastream |
URL de conexión a PostgreSQL |
REDIS_URL |
redis://localhost:6379/0 |
URL de Redis |
SECRET_KEY |
(cambiar) | Clave para firmar tokens JWT |
ALGORITHM |
HS256 |
Algoritmo JWT |
ACCESS_TOKEN_EXPIRE_MINUTES |
10080 (7 días) |
TTL de tokens de admin |
ADMIN_USERNAME |
admin |
Usuario del panel de administración |
ADMIN_PASSWORD |
kiraadmin |
Contraseña del panel (cambiar en producción) |
STREAM_CHUNK_SIZE |
65536 |
Tamaño de chunk en bytes (64 KB) |
STREAM_QUEUE_MAXSIZE |
512 |
Chunks máximos en cola por cliente (≈ 32 MB) |
STREAM_CONNECT_TIMEOUT |
10 |
Timeout de conexión ffmpeg (segundos) |
STREAM_READ_TIMEOUT |
30 |
Timeout de lectura (segundos) |
EPG_REFRESH_HOURS |
12 |
Intervalo de actualización de EPG |
M3U_CACHE_MINUTES |
30 |
Caché de playlist M3U |
LOG_LEVEL |
INFO |
Nivel de log (DEBUG, INFO, WARNING, ERROR) |
Estructura del proyecto
/opt/kirastream/
├── backend/
│ ├── main.py # Punto de entrada FastAPI, lifespan, routers
│ ├── config.py # Settings (pydantic-settings + .env)
│ ├── database.py # Engine async, Base, init_db()
│ ├── api/
│ │ ├── auth.py # Login JWT para el panel admin
│ │ ├── xtream.py # API pública compatible Xtream Codes
│ │ └── admin/
│ │ ├── monitoring.py # WebSocket tiempo real + endpoints slots/streams
│ │ ├── providers.py # CRUD proveedores, sync catálogos, health check
│ │ ├── users.py # CRUD usuarios
│ │ ├── catalogs.py # Catálogos y categorías
│ │ ├── custom_categories.py # Categorías personalizadas
│ │ ├── logs.py # Registro de conexiones y fallos de stream
│ │ ├── epg_admin.py # Gestión EPG
│ │ ├── jellyfin_admin.py # Integración Jellyfin
│ │ └── settings.py # Configuración del servidor
│ ├── core/
│ │ ├── restream.py # BroadcastGroup: ffmpeg pump + fan-out a clientes
│ │ ├── pool.py # ProviderPool: slots, adquisición y liberación
│ │ ├── health_checker.py # Ping periódico a URLs de proveedor
│ │ ├── epg_manager.py # Descarga y fusión de fuentes XMLTV
│ │ ├── catalog.py # Consultas de catálogo por usuario
│ │ ├── probe.py # ffprobe para info de stream (codec, resolución)
│ │ ├── vod_tracker.py # Seguimiento de sesiones VOD/series
│ │ ├── xtream_client.py # Cliente HTTP para API Xtream de proveedores
│ │ └── jellyfin_client.py # Cliente HTTP para Jellyfin
│ ├── models/
│ │ ├── provider.py # ProviderAccount, ProviderUrl
│ │ ├── channel.py # Channel (live/movie/series)
│ │ ├── user.py # User
│ │ ├── log.py # ConnectionLog
│ │ ├── stream_event.py # StreamEvent (fallos y recuperaciones)
│ │ ├── epg.py # EPGSource
│ │ ├── custom_category.py # CustomCategory, CustomCategoryItem
│ │ ├── notification.py # Notification
│ │ └── jellyfin.py # JellyfinConfig, JellyfinItem
│ └── static/ # Frontend compilado (servido por Nginx)
├── frontend/
│ ├── src/
│ │ ├── pages/
│ │ │ ├── Dashboard.tsx # Monitorización en tiempo real
│ │ │ ├── Logs.tsx # Registro de conexiones y fallos
│ │ │ ├── Providers.tsx # Gestión de proveedores
│ │ │ ├── Users.tsx # Gestión de usuarios
│ │ │ ├── Catalogs.tsx # Catálogos
│ │ │ ├── CustomCategories.tsx
│ │ │ ├── EPG.tsx
│ │ │ ├── Jellyfin.tsx
│ │ │ └── Settings.tsx
│ │ ├── components/
│ │ │ └── Layout.tsx # Navegación lateral
│ │ └── lib/
│ │ └── api.ts # Cliente axios con autenticación JWT
│ ├── package.json
│ └── vite.config.ts
├── nginx/ # Configuración de Nginx
├── scripts/
│ └── install.sh # Script de arranque manual
├── logs/ # Logs de la aplicación (no versionados)
├── .env # Variables de entorno (no versionado)
└── .gitignore
API
Autenticación del panel
POST /api/admin/auth/token
Content-Type: application/x-www-form-urlencoded
username=admin&password=...
Devuelve un token JWT que se incluye en las siguientes peticiones como Authorization: Bearer <token>.
API Xtream Codes (pública)
| Endpoint | Descripción |
|---|---|
GET /player_api.php?username=&password=&action= |
API principal Xtream |
GET /get.php?username=&password=&type=m3u_plus |
Playlist M3U |
GET /xmltv.php?username=&password= |
EPG en formato XMLTV |
GET /{user}/{pass}/{stream_id} |
Stream en directo |
GET /live/{user}/{pass}/{stream_id} |
Stream en directo (ruta alternativa) |
GET /movie/{user}/{pass}/{stream_id}.mkv |
VOD |
GET /series/{user}/{pass}/{episode_id}.mkv |
Episodio de serie |
API de administración
| Endpoint | Descripción |
|---|---|
GET /api/admin/monitoring/streams |
Lista streams activos |
GET /api/admin/monitoring/slots |
Estado de slots de proveedor |
WS /api/admin/monitoring/ws |
Actualizaciones en tiempo real |
POST /api/admin/monitoring/kill |
Cortar conexión de un cliente |
GET /api/admin/logs |
Registro de conexiones de usuarios |
GET /api/admin/logs/stats |
Estadísticas agregadas |
GET /api/admin/logs/events |
Registro de fallos y recuperaciones de stream |
DELETE /api/admin/logs |
Limpiar registro de conexiones |
DELETE /api/admin/logs/events |
Limpiar registro de fallos |
GET /api/admin/providers |
Lista de proveedores |
POST /api/admin/providers |
Añadir proveedor |
GET /api/admin/users |
Lista de usuarios |
POST /api/admin/users |
Crear usuario |
GET /api/docs |
Documentación interactiva (Swagger UI) |
Panel de administración
Accesible en http://<servidor>/panel/
Dashboard
Monitorización en tiempo real vía WebSocket. Muestra por cada stream activo:
- Canal, proveedor y tipo (Directo / Película / Serie)
- Resolución, FPS, códecs de vídeo y audio, bitrate
- Estado de conexión y número de reconexiones
- Salud A/V y correcciones automáticas
- Cola cliente — porcentaje de cola del cliente más lento (0 % = consumo sin retraso; 100 % = cliente bloqueado)
- Reserva — capacidad del buffer del servidor (32 MB). Muestra
oken reposo (normal) y los segundos de cobertura si hay datos en cola - Usuarios conectados con opción de cortar conexión individualmente
- Métricas globales: streams activos, slots ocupados, ancho de banda total, CPU de ffmpeg
Registro
Dos pestañas:
Conexiones de usuarios — historial de todas las sesiones con usuario, IP, canal, tipo, duración y datos transferidos. Filtros por usuario, canal, tipo y rango de fechas.
Fallos de stream — registro persistente de eventos del sistema: caídas de proveedor y recuperaciones automáticas. Cada evento incluye canal, proveedor, dominio que falló, mensaje de error y número de intento. Se genera automáticamente; no requiere intervención manual.
Gestión del servicio
# Estado
systemctl status kirastream
# Reiniciar (necesario tras cambios en el backend)
systemctl restart kirastream
# Ver logs en tiempo real
tail -f /opt/kirastream/logs/backend.log
# Actualizar código y redeployar
cd /opt/kirastream
git pull
cd frontend && npm run build && cd ..
systemctl restart kirastream
Registro y monitorización
Logs de la aplicación
/opt/kirastream/logs/backend.log — log estructurado con nivel, módulo y mensaje. Rotación manual recomendada con logrotate.
Eventos de stream
Los fallos y recuperaciones de streams se guardan en la tabla stream_events de PostgreSQL y son visibles en el panel en Registro → Fallos de stream.
Tipos de evento:
| Tipo | Descripción |
|---|---|
caida |
El stream falló por primera vez (ffmpeg no pudo conectar o el proveedor cerró la sesión) |
recuperado |
Tras varios intentos fallidos, el stream estuvo estable más de 30 segundos |
Health check de proveedores
El checker hace ping a cada URL de proveedor cada 5 minutos llamando a /player_api.php?action=user_info y actualiza el estado (ok / error / timeout) y la latencia en milisegundos. El resultado es visible en la sección Proveedores del panel.
Notas técnicas de estabilidad
Reconexión sin pantalla negra
Los proveedores IPTV cierran la sesión cada ~2 minutos como política de gestión de sesiones. Cuando ocurre:
- ffmpeg detecta el cierre de la conexión TCP y termina.
- El loop de Python (
_pump_with_retry) lanza inmediatamente un nuevo proceso ffmpeg con una conexión fresca al proveedor. - La cola del cliente NO se vacía durante la reconexión. Los datos en cola (hasta 32 MB) siguen alimentando al reproductor mientras el nuevo ffmpeg arranca.
- El reproductor no experimenta pantalla negra si la reconexión es más rápida que el buffer local del reproductor.
Por qué no se usa
-reconnect_streameden ffmpeg: ese flag hace que ffmpeg reconecte internamente sin morir. El proveedor re-envía desde el último keyframe (2-5 segundos atrás), lo que produce timestamps PTS hacia atrás que los reproductores interpretan como un rebobinado visible. La reconexión a nivel Python, en cambio, produce siempre una conexión limpia desde el punto de emisión actual.
Buffer del servidor
La cola de cada BroadcastGroup tiene capacidad para 512 chunks de 64 KB = 32 MB por stream. En operación normal la cola está casi vacía (el reproductor consume los datos a la misma velocidad que llegan); eso es correcto y no indica ausencia de protección. La protección real está en que la cola no se vacía activamente durante reconexiones.
Fan-out eficiente
Un único proceso ffmpeg sirve a todos los clientes del mismo canal simultáneamente. Si 10 usuarios ven el mismo canal, solo hay una conexión upstream con el proveedor y un proceso ffmpeg. Los chunks se copian a la cola de cada cliente en el mismo dispatch loop.
Evicción de clientes lentos
Si la cola de un cliente está llena durante más de 30 segundos continuados (red muy lenta o reproductor bloqueado), el cliente es desconectado automáticamente para no perjudicar a otros clientes del mismo stream.
Stack tecnológico
| Capa | Tecnología |
|---|---|
| Backend | Python 3.13, FastAPI 0.115, Uvicorn 0.34 |
| ORM | SQLAlchemy 2.x async + asyncpg 0.30 |
| Base de datos | PostgreSQL 17 |
| Cache / pub-sub | Redis 6+ (redis-py 5.3) |
| Proxy HTTP | aiohttp 3.12 |
| Procesado de stream | ffmpeg 7.1 (copy mode, sin recodificación) |
| Validación | Pydantic 2.13 |
| Frontend | React 18, TypeScript, Vite 6, Tailwind CSS |
| Servidor web | Nginx (proxy inverso + archivos estáticos) |
| Proceso | systemd |