Commit Graph
36 Commits
Author SHA1 Message Date
MichaelandClaude Opus 5.5 4d00409de8 Guide TV : régionales France 3, variantes de nom, chaînes +1, CH et BE
Mesuré en prod après le premier correctif : 69 chaînes sur 149 avec
guide. Les vides restantes relevaient surtout de formes de nom que le
dump écrit autrement, ou de pays non couverts :

- « F3 ALPES » → France 3 Alpes (France.3.-.Alpes.fr) : F2 à F5 développés
- clé souple en dernier recours, dans son propre espace de noms :
  sans code pays ni « channel/tv/hd » (« AB3 » → AB3.Channel.fr)
- chaîne « +1 » absente du dump (TF1 +1) : guide de la chaîne principale
  décalé d'une heure ; une +1 qui a son propre guide le garde
- sources par défaut : FR1 puis CH1 (RTS…) et BE2 (même fournisseur),
  le dump français reste prioritaire

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 23:17:28 +02:00
MichaelandClaude Opus 5.5 abe7d522c1 Guide TV : plus jamais de 500, et bien plus de chaînes couvertes
Constaté en prod : chaque /api/epg répondait 500 en 4 s. Un appel
player_api qui lève (connexion coupée, délai) n'était pas rattrapé, et le
dump XMLTV externe, qui couvre pourtant TF1, France 2, M6…, n'était jamais
consulté. Et 59 % des chaînes françaises n'ont pas d'epg_channel_id : leur
nom brut (« FR - TF1 FHD ◉ ») ne correspondait à aucune entrée XMLTV.

- échec player_api rattrapé : la chaîne passe à la source suivante
- dump externe (déjà en mémoire) avant l'appel lent chaîne par chaîne
- correspondance par nom nettoyé : préfixe pays, FHD/HD/UHD/4K/HEVC…,
  symboles retirés, forme « nom.pays » tentée ; « +1 » conservé
- tous les display-name XMLTV indexés, plus seulement le premier
- table des chaînes : un seul téléchargement partagé, décodé hors de la
  boucle principale, et un échec n'est plus gardé 6 h (2 min)
- guide vide gardé 5 min au lieu de 30

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:51:24 +02:00
MichaelandClaude Opus 5.5 8d79472ca8 Démarrage : la lecture du quota ne fait plus attendre le flux
AccountLimits interrogeait player_api.php avant d'ouvrir le flux quand
sa valeur n'était pas en cache (premier flux, puis toutes les 10 min).
Le panneau met jusqu'à 4 s à répondre (mesuré en prod) : autant de
retard au démarrage d'une chaîne ou d'un film.

La valeur connue, même périmée, ou le repli (1) est rendue tout de suite ;
la lecture chez le panneau se fait en arrière-plan, une seule à la fois.
Sans risque : le quota ne sert qu'à choisir quels flux orphelins couper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:34:26 +02:00
MichaelandClaude Opus 5.5 6812a4964d Lecture : ne couper que les flux orphelins, jamais un flux regardé
La règle « le dernier flux gagne » coupait aussi un flux en cours de
lecture. Deux lecteurs ouverts sur un compte à une connexion se coupaient
alors en boucle (chacun relance aussitôt) : plus aucune image nulle part.
Constaté en prod : tant qu'un autre onglet lisait, chaque ouverture
échouait après 5 s sans un octet.

Une connexion n'est désormais coupée que si son spectateur ne donne plus
signe de vie depuis 15 s : aucune requête de playlist/segment (HLS, film)
ni octet remis au client (turbo.ts). Les orphelins partent toujours, un
second lecteur actif est refusé par le panneau comme avant.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 22:13:58 +02:00
MichaelandClaude Opus 5.5 662e1034e5 Lecture : couper le flux orphelin avant d'en ouvrir un nouveau
L'abonnement n'autorise qu'une connexion simultanée (max_connections = 1,
vérifié en prod). Or un FFmpeg de lecture survivait au spectateur : 4 min
pour un direct HLS, jusqu'à 15 min ou la fin du téléchargement pour un
film, le temps de détecter la déconnexion pour turbo.ts. Le flux suivant
trouvait le slot pris : HTTP 551 ou timeout côté panneau (film qui ne
démarre pas, zap qui échoue).

Avant d'ouvrir une connexion amont, le serveur coupe désormais les plus
anciennes connexions de lecture du même compte, dans la limite du quota
max_connections lu chez le fournisseur (cache 10 min, 1 par défaut). Les
enregistrements ne sont jamais coupés.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 11:51:56 +02:00
MichaelandClaude Opus 5.5 327873d254 Journaux : masquer les identifiants Xtream des URL et exceptions
Le repli XMLTV journalisait l'URL `xmltv.php?username=…&password=…` du
panneau en clair, et le texte des `ClientException` / `ProcessException`
recopiait aussi l'URL : les identifiants de l'abonné finissaient dans
`docker logs`. Passage par `LogRedactor.redactUrl` dans XmltvEpgService,
EpgApi, le relais live, FFmpegSessionManager et RecordingScheduler.

Test : capture des `print` via une Zone pour vérifier le masquage.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 09:25:00 +02:00
MichaelandClaude Opus 5.5 da67c479b3 Guide TV : indexer le XMLTV hors de la boucle principale
Le serveur n'a qu'une boucle d'événements. La décompression et le
parsing des dumps XMLTV (7000+ chaînes, ~90 Mo) la gelaient pendant
plusieurs secondes : plus rien ne répondait, y compris le relais
turbo.ts. La lecture se coupait donc au démarrage, au moment où
l'écran des chaînes demandait le guide (EPG à 19-26 s dans les logs).

- Téléchargement inchangé, puis gunzip + utf8 + parse dans Isolate.run
- _parse devient statique (aucune capture de this, client HTTP non
  transférable)
- Test : la boucle d'événements ne doit pas geler plus de 250 ms
  pendant l'indexation d'un dump de taille réelle (1,65 s avant)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 09:11:54 +02:00
MichaelandClaude Opus 5.5 db13c85c1d Lecture : repli CPU quand le GPU sature, plus de spinner infini
Serveur chargé ou VRAM limite : NVENC refuse d'ouvrir l'encodeur
(OpenEncodeSessionEx failed: out of memory), FFmpeg meurt en une
seconde et la playlist part en 502. Chaque relance retombait sur le
même GPU saturé : le flux restait illisible.

- Serveur : un échec imputable au GPU (NVENC, CUDA, mémoire) relance
  la session une fois en libx264, sous le même identifiant — les
  segments suivent sans que le lecteur s'en aperçoive. Direct, VOD et
  enregistrements
- Le GPU est écarté 2 min après une panne : les sessions suivantes
  partent directement sur le CPU au lieu d'échouer une à une
- Source injoignable ou délai dépassé : pas de repli, il doublerait la
  charge d'un serveur déjà occupé
- Process.start qui échoue (serveur à court de ressources) : 502/503
  au lieu d'une exception remontée en 500
- Client : une erreur réseau fatale avant la première playlist
  appelait hls.startLoad(), qui ne relance pas ce chargement → spinner
  infini, aucune erreur, relances de page jamais déclenchées. L'erreur
  remonte désormais à la page
- mpegts.js : au-delà de 3 erreurs en 60 s, bascule sur HLS au lieu de
  recréer sans fin (un FFmpeg neuf toutes les 1,2 s)
- player_lite.html : 3 relances avant l'erreur, comme les autres pages

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-08 21:28:26 +02:00
MichaelandClaude Opus 5 9843999809 Logos des chaînes : repli par nom quand l'hébergeur de picons tombe
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>
2026-09-19 13:39:01 +02:00
MichaelandClaude Opus 5 b6ca9dab96 Logos, guide TV et fluidité : couper le flot de requêtes mortes
Logos absents : l'hébergeur des picons du fournisseur répond 503 sur
tout, y compris sa racine (serveur en maintenance). Le proxy relayait
ce 503 au navigateur, qui redemandait chaque logo à chaque affichage de
la grille — des centaines de requêtes sortantes mortes par seconde.
- Un statut >= 400 sur une image rend désormais un pixel transparent
  avec `Cache-Control: max-age=600`, au lieu de propager l'erreur
- Après trois échecs consécutifs, l'hôte est marqué hors service cinq
  minutes : plus aucun appel sortant, réponse servie localement
- Le corps d'erreur amont est drainé, sinon la connexion restait
  ouverte jusqu'au timeout

Guide TV très lent (4 à 8 s par chaîne dans les logs) : `player_api`
demande un appel par chaîne, et le panneau met ~4 s à répondre — une
grille de trente chaînes dépassait la minute.
- Le dump `xmltv.php` du panneau passe en premier : une requête couvre
  toutes les chaînes, réutilisée trois heures
- L'interrogation chaîne par chaîne reste le repli, puis la source
  XMLTV externe
- Une source XMLTV en échec n'est plus retéléchargée à chaque
  consultation : quinze minutes de recul
- Index borné à 48 h en avant : un dump national couvre sept jours pour
  un millier de chaînes, soit des centaines de Mo pour un guide qui
  n'affiche que le programme courant

Lecture saccadée : le relais FFmpeg -> navigateur de `/turbo.ts` ne
transmettait pas la pause du client à la source. Un lecteur plus lent
que le flux laissait FFmpeg produire à pleine vitesse et le serveur
empilait les paquets en mémoire, d'où la dérive derrière le direct. La
contre-pression est maintenant propagée (`pipeWithBackpressure`).

Le serveur tourne sur un seul isolate : le flot de requêtes mortes et
les attentes EPG de plusieurs secondes partageaient la boucle
d'événements avec les paquets vidéo.

Tests ajoutés : cache d'échecs par hôte, contre-pression du relais,
priorité des sources EPG, recul après échec XMLTV, horizon d'index.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 11:57:38 +02:00
Claude fa8eed38f2 Enregistrements : corriger le démarrage de la lecture
La lecture d'un enregistrement démarrait au milieu du programme et se
coupait aussitôt ; il fallait relancer une deuxième fois pour qu'elle
tienne.

Cause principale, introduite avec le passage de la playlist en `EVENT` :
tant que le transcodage n'est pas terminé, la playlist n'a pas
d'`EXT-X-ENDLIST`, et hls.js traite alors tout contenu comme du direct.
Sans `startPosition` explicite il démarre au « bord du direct » — donc
collé au front d'encodage, sans la moindre avance de segments. D'où un
départ en plein milieu, puis une coupure immédiate. Au second essai, le
transcodage était terminé (ENDLIST présent, contenu traité en VOD) et
tout se passait bien.

Hors direct : `startPosition: 0` (ou la position de reprise), rattrapage
de latence neutralisé (il accélérait la lecture puis forçait un saut en
avant vers un direct inexistant), et délai d'attente de playlist porté de
10 s à 45 s — une session FFmpeg qui démarre n'est plus prise pour un
manifeste mort.

Cause de fond : la lecture ré-encodait la vidéo. Or la capture se fait en
`-c copy`, donc le fichier contient le codec de la chaîne, presque
toujours du H.264, directement lisible en HLS. Le ré-encodage tenait à
peine le temps réel en 1080p, ce qui laissait le lecteur courir après
l'encodeur en permanence. La vidéo est désormais copiée telle quelle
quand elle est en H.264 (seul l'audio est converti en AAC) : la
segmentation va à la vitesse du disque, l'enregistrement devient
navigable en quelques secondes et les sauts ne redémarrent presque plus
jamais FFmpeg. Les codecs que le navigateur ne sait pas lire (HEVC,
MPEG-2…) restent ré-encodés.

En complément :
- le serveur attend trois segments d'avance avant de servir la playlist,
  et sert ce qu'il a plutôt qu'une erreur si le délai expire ;
- le lecteur de bureau réessaie seul jusqu'à 3 fois, comme le lecteur
  mobile le faisait déjà ;
- `XFPlayer.destroy()` retire ses écouteurs : sans ça, une relance
  laissait l'instance précédente réagir aux commandes du parent et
  republier des positions périmées ;
- les appels ffprobe passent par `MediaProbe`, qui mémorise durée et
  codec tant que le fichier ne change pas.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1A6WRnoktWcT7ZG4b2rbC
2026-09-10 17:48:02 +00:00
Claude b95d72fe80 Enregistrements : lecteur avancé, navigation et reprise
La lecture d'un enregistrement ne permettait ni de reculer, ni d'avancer,
ni de reprendre où on s'était arrêté.

Cause côté serveur : la playlist était servie en
`EXT-X-PLAYLIST-TYPE:VOD`. hls.js la considère alors comme définitive et
ne la relit jamais — il ne voyait donc que les quelques secondes déjà
transcodées à l'ouverture, d'où une durée absurde et une barre de
progression inutilisable. Elle passe en `EVENT` : la zone navigable
grandit au rythme de l'encodage.

FFmpeg transcodant séquentiellement, sauter à 45 min imposait malgré tout
d'attendre qu'il y arrive. La playlist accepte donc `?start=<secondes>` et
démarre une session dédiée (`-ss` avant `-i`). Les segments y sont
préfixés par l'offset (`t2700/segment_000.ts`) : la query string ne
survit pas aux URLs relatives de la playlist, deux sessions du même
enregistrement se seraient mélangées. Les sessions d'offsets abandonnés
sont fermées dès qu'aucun lecteur ne les consomme (killIdleSiblings).

Côté lecteur, tout ce qui sort du moteur est désormais en temps absolu
(`offset` réinjecté) : une recherche dans la zone déjà transcodée est un
simple `currentTime`, au-delà le lecteur répond `seek_out_of_range` et
l'app relance le flux à cet instant.

Ajouts visibles :
- reprise proposée à l'ouverture d'un enregistrement entamé, et mention
  « Reprendre à … » avec sa progression dans la liste ;
- durée réelle mesurée par ffprobe (`duration_seconds`), mise en cache
  tant que le fichier ne bouge pas — `end_time - start_time` n'est que la
  durée programmée ;
- sauts ±10 s et ±1 min, sélecteur de vitesse (0,5× à 2×), portion déjà
  transcodée visible sur la barre ;
- raccourcis K/espace, J/L, Maj+←/→, 0-9, F, M, virgule/point.

L'identifiant de vue de plateforme porte maintenant un numéro de
génération : sans lui, réutiliser le même identifiant laissait l'ancienne
iframe en place (`registerViewFactory` conserve la première fabrique
enregistrée) — ce qui affectait déjà le retour sur une chaîne
précédemment zappée.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01T1A6WRnoktWcT7ZG4b2rbC
2026-09-10 16:00:13 +00:00
Claude e5079127ab Merge remote-tracking branch 'origin/main' into claude/lecture-video
# Conflicts:
#	CHANGELOG.md
2026-08-28 06:57:42 +00:00
Claude 85a8d16542 Lecture vidéo : son garanti sur toutes les chaînes, zapping et démarrages accélérés
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
2026-08-27 21:17:27 +00:00
Claude 1d8717bb40 Fiabilise le système d'enregistrement de bout en bout
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
2026-08-27 12:11:56 +00:00
Claude f798b43a1e fix(enregistrements): un enregistrement mené à terme n'est plus marqué « échoué »
Chaque enregistrement arrivé au bout de sa fenêtre était marqué
« Échoué / Interruption inattendue du serveur » alors que le fichier était
bien sur le disque : le tick lisait la base AVANT d'arrêter les captures
terminées, si bien que l'instantané annonçait encore « recording » pour un
enregistrement déjà retiré de la table des processus actifs — la détection
d'orphelin le requalifiait aussitôt en échec. La base est désormais lue
après les arrêts, et la requalification revérifie le statut courant.

Deux autres façons de perdre un enregistrement sont corrigées au passage :

- FFmpeg livre encore des morceaux de stderr après la résolution de
  `exitCode` ; écrire sur l'`IOSink` déjà fermé levait une `StateError`
  depuis un callback de stream, donc une erreur asynchrone non rattrapée
  qui tue l'isolate — et avec lui le serveur et toutes les captures en
  cours. Le log passe par un écrivain tolérant et n'est fermé qu'une fois
  stdout et stderr drainés.
- Une coupure amont terminait la capture définitivement. FFmpeg reçoit
  maintenant les options de reconnexion (comme le proxy live), et le
  planificateur relance la capture sur la fin de fenêtre quand le process
  sort trop tôt (backoff 3→30 s, quota remis à zéro après une capture
  saine). Les parties issues des relances sont recollées dans le fichier
  principal via le demuxer `concat`, la lecture reste donc un seul fichier.

Également :
- reprise des captures interrompues par un redémarrage du conteneur tant
  que la fenêtre est ouverte, au lieu d'un échec sec ; fichier partiel
  conservé (statut « terminé ») quand la fenêtre est passée
- `-t` calculé sur le temps restant jusqu'à la fin programmée : un
  démarrage tardif ne rogne plus la fin du programme
- `-hide_banner -nostats` : le log d'enregistrement redevient lisible (et
  ne pèse plus des mégaoctets, il est relu en entier par l'API)
- `RECORDINGS_DIR` et `FFMPEG_PATH` surchargeables, et le motif d'erreur
  est effacé au (re)démarrage d'une capture

Vérifié avec un faux ffmpeg sur un planificateur réel : avant correctif,
un enregistrement mené jusqu'à la fin de fenêtre ressort « failed /
Interruption inattendue du serveur » ; après, « completed » — de même que
la reprise après interruption, la relance après coupure et la fusion des
parties.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E5xWYTbtZwJuYB3E51K243
2026-08-26 19:09:31 +00:00
MichaelandClaude Opus 5 5b9eb74a84 feat(epg): source XMLTV de repli quand le panneau ne rend rien d'actuel
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>
2026-08-11 10:48:10 +02:00
MichaelandClaude Fable 5 9595afcc99 fix(streaming): stop streams dying mid-playback
- reaper no longer kills exited sessions immediately: a finished VOD
  transcode was reaped (segments deleted) while still being watched
- reuse completed VOD/recording sessions instead of re-transcoding
- live input: add -rw_timeout 30s and -reconnect_at_eof so a stalled
  upstream triggers reconnect instead of wedging ffmpeg forever
- reaper watchdog restarts live sessions whose playlist stopped updating
- waitForPlaylist fails fast on clean ffmpeg exit without output
- direct .ts proxy: close http.Client on stream end/error (socket leak)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 14:34:31 +02:00
MichaelandClaude Fable 5 60d3f42901 feat: security hardening, streaming overhaul, design polish, tests
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>
2026-06-10 10:07:18 +02:00
Michael c61984d041 feat: implement RecordingScheduler to manage scheduled recordings and automated season passes 2026-04-02 19:12:21 +02:00
Michael 1b4321d84a feat: implement SimpleRecorder service for FFmpeg-based stream recording and scheduling 2026-04-02 07:22:24 +02:00
Michael 6a4a642611 feat: implement IPTV player screen with iframe-based stream support and recordings tab widget 2026-04-01 20:19:40 +02:00
Michael fa6ae90d8d feat: add mobile-optimized IPTV player with HLS and MpegTS support 2026-03-29 10:19:36 +02:00
GitHub Copilot 2c4572191d feat(recording): implement simplified recording system with API endpoints and UI integration 2026-03-26 11:00:40 +01:00
Michael 9f3c29f6dd feat: implement Xtream API service for managing live channels, VOD, and series with caching and dynamic backend URL handling. 2026-03-10 14:57:44 +01:00
Michael 50a6998c09 feat: add EPG TV guide, season passes (auto-recording) and unified recordings/EPG tab with 3 sub-tabs 2026-03-09 08:21:48 +01:00
Michael 4762b4edf8 feat(recordings): switch output format from mp4 to mkv for better IPTV stream compatibility 2026-03-08 23:56:43 +01:00
Michael 4c2e3ce600 feat: add stop recording button and POST /api/recordings/stop/<id> endpoint to kill FFmpeg process 2026-03-08 23:53:32 +01:00
Michael 424019d01a fix(scheduler): wrap _startRecording in single try/catch to prevent server crash, fix localhost port 8080→8089 2026-03-08 23:38:57 +01:00
Michael c04acc2fab debug(scheduler): add verbose logging every 10s and force UTC datetime comparisons 2026-03-08 23:11:22 +01:00
Michael 60bb10fe4d fix(recordings): correct stream_url to use real live stream, resolve to absolute URL for FFmpeg, use copy mode, speed up scheduler to 10s 2026-03-08 22:43:42 +01:00
Michael f217622e4a fix(recording): create log file before ffmpeg starts to capture boot exceptions and force absolute path 2026-03-08 20:50:38 +01:00
Michael 966311f560 feat(recording): add ffmpeg logs writing and viewing in UI 2026-03-08 19:26:54 +01:00
Michael 2180572b92 feat: implement live tv recording system 2026-03-08 08:40:55 +01:00
Michael 6a71f58a36 feat: add new UI tabs for movies and series, including mobile-specific versions with search and pagination. 2025-12-08 20:43:41 +01:00
Michael d1fbfe0f1e feat: Add initial server implementation with API routing, security middleware, and a file cleanup service. 2025-12-07 22:49:29 +01:00