L'hébergeur des picons du fournisseur (51.158.145.100) répond toujours
503 sur toutes ses URL. Couper le flot de requêtes ne suffisait pas : la
grille restait sans logo. Pire, le pixel transparent servi en 200 était
pris pour une image valide par Flutter, qui n'affichait plus son icône
de repli — d'où des tuiles entièrement vides.
- Nouvelle route `/api/logo?src=…&name=…` : URL du panneau d'abord (via
le proxy, mêmes protections anti-SSRF et même mémoire des hôtes
morts), puis recherche par nom dans le dépôt public tv-logo/tv-logos
- Appariement par nom : préfixe pays et suffixes de qualité ignorés,
`+` → `plus`, variante plus longue (`bein-sports-1-french`), puis
marque sans numéro pour les canaux événementiels. Aucune approximation
sur un mot seul
- Liste de logos en cache 24 h (API GitHub : 60 requêtes/h non
authentifiées), 15 min de recul après échec, images en mémoire
- Image indisponible : 410 mis en cache au lieu du pixel. 410 et non
404 : la Cascade du serveur rattrape les 404 et renvoyait celui du
handler statique, sans cache-control
- Client (grille bureau, mobile, enregistrements) : logo toujours
demandé au backend, nom compris, même sans stream_icon. L'onglet
Enregistrements chargeait l'URL http brute, bloquée en HTTPS
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
L'écran « Échec du chargement : vendor/mpegts.min.js » était un cul-de-sac :
la moindre requête ratée sur la lib condamnait la chaîne, alors que le flux
restait parfaitement lisible autrement.
- xf-player-core : le chargement des libs est retenté une fois en
contournant le cache HTTP (les libs sont servies en max-age=86400, une
entrée tronquée bloquait le lecteur jusqu'au vidage manuel du cache), et
seul un succès est mémorisé — la promesse rejetée restait en cache et
rendait tout réessai impossible pour le reste de la session.
- Repli automatique : si mpegts.js reste introuvable, le live bascule sur la
route HLS équivalente (où FFmpeg réencode déjà l'audio en AAC) ; si hls.js
reste introuvable, on tente le HLS natif. Plus d'écran d'erreur quand un
chemin de lecture est encore disponible.
- Message d'erreur exploitable : la cause réelle est affichée (HTTP 404,
429, réseau injoignable) au lieu d'un échec indifférencié.
- Préchargement corrigé : les trois players préchargeaient hls.js en dur,
soit 618 Ko téléchargés pour rien à chaque zap TV — c'est mpegts.js qui
sert au direct — au détriment du flux et du chargement de mpegts.js
lui-même. Le préchargement suit désormais le flux demandé, avec la même
décision que XFPlayer.start() (et ne charge rien sur Safari/iOS).
- Serveur : avertissement explicite au démarrage si web/vendor/*.min.js
manque, au lieu de laisser le navigateur échouer sans explication.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FSovdudi3KBwZK3aC42fw
Son manquant sur certaines chaînes :
- Nouvelle route GET /api/live/<id>/turbo.ts pour le player web : vidéo
copiée (-c:v copy), audio TOUJOURS réencodé en AAC. mpegts.js ne démuxe
que l'AAC/MP3 : les chaînes en AC-3/E-AC-3/MP2 passaient par le proxy
brut → image sans son, et le fallback HLS côté client ne couvrait pas
les codecs démuxés mais non décodables par le navigateur (MP2)
- getLiveStreamUrlTs pointe sur la route turbo ; le proxy brut
/api/live/<id>.ts reste inchangé pour le scheduler d'enregistrement
- FFmpeg turbo tué dès que le client zappe (onCancel) ; stderr redacté
- Le fallback client (_hlsEquivalent) reconnaît le nouveau format d'URL
et reste en filet de sécurité
Temps de chargement :
- Players vendorisés mis à jour : hls.js 1.6.7 → 1.7.1,
mpegts.js 1.7.3 → 1.8.2
- Live HLS et turbo : -fflags nobuffer + probesize/analyzeduration 1 Mo
(sans borne, FFmpeg pouvait sonder plusieurs secondes avant le premier
segment)
- VOD : probesize 10 Mo → 5 Mo, analyzeduration 5 s → 2 s
- Live 'high' : preset medium → veryfast (zerolatency déjà actif) ;
lecture d'enregistrement : medium/crf18 → veryfast/crf20 (medium ne
tenait pas le temps réel en 1080p sur CPU modeste)
- waitForPlaylist : polling 500 ms → 100 ms
- Suppression du cache-buster &v=timestamp qui re-téléchargeait
player.html à chaque zap ; les .html sont servis en no-cache côté
serveur pour garder la fraîcheur après déploiement
- mpegts.js : liveBufferLatencyChasing activé en profil « fast » — les
micro-coupures ne font plus dériver la lecture derrière le direct
Validé : dart analyze (0 issue) + dart test (48/48) sur bin/, syntaxe JS
vérifiée.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015oEu9QayWsw7hCKhenxgVa
Proxy /api/xtream :
- Authentification de session rétablie (Authorization ou cookie HttpOnly
session — les requêtes navigateur même-origine le portent) ; le proxy
était volontairement ouvert, offrant un rebond SSRF non authentifié
- Redirections suivies manuellement avec revalidation à chaque saut
(hôte privé interdit + allowlist de domaine) : avec followRedirects,
seule l'URL initiale était validée, une 302 amont suffisait pour
atteindre un hôte interne
- Erreurs proxy sans détail d'exception (ClientException porte l'URL
amont, credentials Xtream inclus), logs redactés
Logs :
- redactedLogRequests remplace logRequests() de shelf : l'URI de
/api/xtream/<url> écrivait username/password Xtream en clair à chaque
requête, annulant l'effort de LogRedactor partout ailleurs
- Les 500 d'epg_api ne renvoient plus e.toString() au client (même
risque ClientException) ; détail redacté en log serveur
Middleware :
- X-Forwarded-For honoré uniquement depuis un proxy de confiance
(loopback + RFC1918 par défaut, surchargables via TRUSTED_PROXIES) :
un client direct forgeait l'en-tête et contournait le rate limit
global comme la limite de tentatives de login
- Honeypot comparé sur chemin exact/préfixe : l'ancien
contains(trap.replaceAll('/','')) bloquait toute URL contenant
console, env ou wpadmin, y compris des URLs proxifiées légitimes
Comptes :
- Mot de passe admin initial aléatoire (Random.secure, affiché une fois
au démarrage) ou ADMIN_INITIAL_PASSWORD ; fini le admin/admin persistant
- Longueur minimale de 8 caractères à la création et au changement
Divers :
- CleanupService ne cible plus Directory.systemTemp en récursif (il
supprimait les temporaires de la VM Dart et le parent des sessions HLS)
- web/xf-player-core.js : postMessage vers location.origin au lieu de
'*', et filtrage d'event.origin à la réception (le côté Flutter le
faisait déjà)
Validé : dart analyze (0 issue) et dart test (48/48) sur bin/.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015oEu9QayWsw7hCKhenxgVa
Fuseaux horaires :
- POST /api/recordings rejette en 400 les dates sans fuseau et normalise
tout en UTC à l'écriture (l'interprétation des dates naïves dans le TZ
du conteneur décalait les enregistrements de 1-2 h)
- Helper frontend unique postRecording() : les 2 points de création
(modal, guide EPG) envoient la même convention UTC
Contrôle d'accès :
- stop/delete/logs d'un enregistrement et delete d'un season pass
vérifient la propriété (userId ou admin), comme playlists_handler
- Suppression des replis 'dev_user_id' et 'admin' (401 sans session)
SQLite :
- PRAGMA foreign_keys/WAL/busy_timeout (les ON DELETE CASCADE déclarés
ne s'appliquaient pas : sessions et playlists orphelines)
- Migrations de schéma versionnées (schema_version) + index user_id,
start_time, season_passes(user_id)
Gestion disque :
- Refus explicite de capture sous MIN_FREE_DISK_MB (défaut 500 Mo)
- Rotation par quota d'octets (RECORDINGS_QUOTA_GB, opt-in) qui ne touche
jamais un enregistrement actif et supprime fichiers + ligne ensemble ;
l'ancienne rotation « 50 fichiers » pouvait effacer une capture en cours
- DELETE /api/recordings/<id> supprime aussi .mkv/.log/parties (SafePath)
Scheduler :
- Arrêt gracieux orchestré par server.dart : clôture des enregistrements
(fusion des parties, statut) avant killAll des sessions de streaming ;
l'ancien handler SIGTERM de FfmpegSessionManager faisait exit(0) direct
- Noms de fichiers uniques par fragment d'id (deux enregistrements du
même programme s'écrasaient mutuellement avec -y)
- Requête filtrée scheduled/recording au lieu de toute la table / 10 s
- Statut cancelled pour un scheduled arrêté (completed sans fichier
cassait la lecture)
Season passes :
- Playlist du propriétaire du pass résolue à chaque scan (l'injection
figée du 1er utilisateur rendait les passes muets après ajout de
playlist, et mélangeait les credentials en multi-utilisateurs)
- Correspondance exacte par défaut (match_mode, migration en 'contains'
pour l'existant), plafond de créations par scan, réalignement des
horaires déplacés dans l'EPG, déduplication tolérante ±2 min
- Redaction des erreurs de scan (ClientException contient l'URL amont)
API de suivi (polling conservé) :
- GET /api/recordings enrichi : progress_pct, file_size_bytes,
retry_count, is_active ; barre de progression + taille dans la liste
Validé : dart analyze (0 issue) et dart test (48/48) sur bin/.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015oEu9QayWsw7hCKhenxgVa
Le guide du fournisseur est figé sur beaucoup de revendeurs : sur le compte de
test, get_simple_data_table répond correctement mais le dernier programme
français date de deux jours, et le xmltv.php du panneau ne couvre aucune chaîne
française. Aucun correctif côté appel Xtream ne pouvait donc remplir la grille.
Nouveau service XmltvEpgService : téléchargement de dumps XMLTV publics,
décompression gzip détectée sur le nombre magique, parsing en flux via
XmlEventReader — un dump national fait 46 Mo décompressés, en charger l'arbre
DOM coûterait plusieurs centaines de mégaoctets dans le conteneur. Mesuré à
environ 1 s pour 973 chaînes. Index rafraîchi toutes les 6 h, programmes
terminés depuis plus de 6 h écartés à l'indexation.
L'appel sortant reste un dernier recours. L'ordre est inchangé —
get_simple_data_table, puis get_short_epg — et le dump n'est consulté que si
le panneau ne rend aucun programme couvrant l'instant présent : compter les
entrées ne suffisait pas, un guide périmé en renvoie des centaines. La source
se configure par EPG_XMLTV_URLS ; vider la variable supprime tout appel
sortant. La réponse porte un en-tête X-Epg-Source pour savoir qui a répondu.
Les identifiants sont rapprochés après normalisation : le dump écrit
France.2.fr là où le panneau annonce France2.fr. Repli sur le nom affiché de
la chaîne, et table stream_id vers identifiant EPG mise en cache 6 h.
Le client passe désormais par /api/epg au lieu d'appeler player_api.php en
direct. Il dupliquait le choix de l'action, la conversion des fuseaux et le
décodage base64 ; le backend le fait une fois, met en cache pour tous les
utilisateurs, et sait basculer sur le repli — bascule impossible depuis le
navigateur.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- Stop sending no-store on vendored player libraries: hls.min.js and
mpegts.min.js (~750 KB) were re-downloaded on every player open
- hls.js: start with 1 live segment instead of 3, lower buffer-first
threshold 1.5s -> 0.8s, poll buffer every 100ms
- mpegts.js: initial stash 512KB -> 128KB, start at 0.5s buffered
- Live FFmpeg: -hls_init_time 1 closes the first segment after ~1s
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Security:
- Replace unsalted SHA-256 password hashing with bcrypt (lazy rehash on login)
- Add authenticated /api/xtream-api gateway: Xtream credentials are injected
server-side and never sent to the frontend; /api/playlists no longer
returns passwords
- Redact credentials from all logs (login body, proxy/FFmpeg/scheduler URLs)
- Add auth to recordings, EPG, season-passes and streaming routes
(HttpOnly session cookie for hls.js; loopback bypass for local FFmpeg)
- Lock player postMessage to same-origin in both directions
- Vendor and pin hls.js 1.6.7 / mpegts.js 1.7.3 (drop CDN @latest)
- Fix rate limiter (client IP was never resolved), add login rate limit,
restrict CORS, add CSP Report-Only, block private-IP SSRF targets,
fix path traversal in recording log retrieval, chmod 777 -> 770
- Remove dead HiveService (seeded admin/admin into IndexedDB with SHA-256)
- Fix authMiddleware not populating 'user' context (getPlaylist ignored the
logged-in user; admin purge always returned 403)
Streaming:
- New FfmpegSessionManager: process registry, idle reaper (4 min live /
15 min VOD), orphan cleanup at startup, clean SIGTERM shutdown,
fast-fail with stderr instead of 30 s timeout
- Quality selection (source/high/medium/low) for live and VOD; source mode
streams with -c:v copy (zero transcoding); selector wired into the player
- Concurrent recordings (MAX_CONCURRENT_RECORDINGS, default 2); conflicts
retry on the next tick instead of silently failing
- Lower live latency (HLS window 20 -> 10 segments, liveSync 10 -> 3)
- Fix recording log lookup (.mp4 vs .mkv mismatch)
Design:
- Replace hardcoded colors with AppColors tokens (12 files)
- web/theme.css syncs HTML players with the Flutter palette
- DPAD/keyboard navigation (arrow-key focus, player shortcuts)
- Tooltips on player icon buttons, Semantics on content cards
- Remove 7 dead widgets broken since the Stitch merge
Quality:
- bin/test/: 21 unit tests (bcrypt, redaction, traversal, SSRF, recording
conflicts) plus a quality-selector widget test
- GitHub Actions CI (analyze + test + build web)
- Archive stale status docs into docs/archive/
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>