# 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](#características) - [Arquitectura](#arquitectura) - [Requisitos](#requisitos) - [Instalación](#instalación) - [Configuración](#configuración) - [Estructura del proyecto](#estructura-del-proyecto) - [API](#api) - [Panel de administración](#panel-de-administración) - [Gestión del servicio](#gestión-del-servicio) - [Registro y monitorización](#registro-y-monitorización) - [Notas técnicas de estabilidad](#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 1. El reproductor solicita `GET /{user}/{pass}/{stream_id}`. 2. `ProviderPool.acquire()` busca o crea un `BroadcastGroup` para ese canal. 3. El grupo lanza un proceso ffmpeg que lee del proveedor y escribe MPEG-TS en stdout. 4. Cada chunk (64 KB) se despacha a las colas asyncio de todos los clientes conectados. 5. El `StreamingResponse` de FastAPI lee de la cola y lo envía al reproductor via HTTP chunked. 6. 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 ```bash apt install -y python3 python3-venv python3-pip \ postgresql redis-server nginx ffmpeg \ nodejs npm ``` ### 2. Clonar el repositorio ```bash git clone https://git.kiracloud.es/joaquin/KiraTV.git /opt/kirastream cd /opt/kirastream ``` ### 3. Entorno Python ```bash 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 ```bash # 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`: ```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 ```bash 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: ```bash cp /etc/nginx/sites-available/default /etc/nginx/sites-available/default.bak ``` Contenido de `/etc/nginx/sites-enabled/default`: ```nginx 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; } ``` ```bash nginx -t && systemctl reload nginx ``` ### 8. Servicio systemd Crear `/etc/systemd/system/kirastream.service`: ```ini [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 ``` ```bash 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 `. ### 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:///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 `ok` en 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 ```bash # 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: 1. ffmpeg detecta el cierre de la conexión TCP y termina. 2. El loop de Python (`_pump_with_retry`) lanza inmediatamente un nuevo proceso ffmpeg con una conexión fresca al proveedor. 3. **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. 4. 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_streamed` en 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 |