281 Commits
Author SHA1 Message Date
MichaelandClaude Opus 5.5 6368fd6ee2 Multi-IPTV : déclarer l'IPTV active de façon synchrone dans AuthedHttp
AuthedHttp lisait l'IPTV dans localStorage, écrit par un écouteur
Riverpod enregistré tardivement : après un changement d'IPTV l'en-tête
X-Playlist-Id pouvait rester périmé. AuthedHttp.usePlaylist pose l'IPTV
en mémoire dès la sélection et à chaque construction du tableau de bord.

Auto NBA reçoit la playlist, recharge sa règle quand elle change et
nomme l'IPTV sur laquelle les matchs sont enregistrés.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 18:16:35 +02:00
MichaelandClaude Opus 5.5 a6322b57b8 Multi-IPTV : changer d'IPTV depuis le rail reste sur le tableau de bord
La route /dashboard faisait ref.watch(selectedPlaylistProvider) avec le
ref du routerProvider : le GoRouter entier dépendait de la playlist. Un
choix dans le sélecteur du rail recréait le routeur, reparti de /login
puis redirigé vers /playlists.

Un Consumer limite la reconstruction à la route, et une clé par IPTV
réinitialise les onglets, qui ne chargent leur catalogue qu'en initState.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 18:16:35 +02:00
MichaelandClaude Opus 5 ccd983d968 Multi-IPTV : nommer l'IPTV active et en changer depuis le rail
Rien à l'écran ne disait sur quelle IPTV on travaillait. Guide,
enregistrements et Auto NBA appartiennent pourtant chacun à une playlist,
et deux playlists montrant le même contenu passaient pour un défaut de
filtrage alors que la sélection elle-même était invisible. Changer d'IPTV
demandait en plus de descendre dans les Réglages.

- PlaylistSelectorButton : nom de l'IPTV active sous son icône dans le
  rail du tableau de bord, et menu de bascule au clic. Écrit comme
  QualitySelectorButton, l'autre sélecteur du projet — même
  PopupMenuButton sous Tooltip, même surfaceContainerHigh, même rayon 16,
  mêmes marques radio. La bascule passe par selectPlaylist, donc le choix
  est persisté avant la première requête.
- Son décalage vaut 68 et non les 56 du menu Profil : ce dernier est
  mesuré depuis un avatar de 40 centré dans le rail de 80, ce
  déclencheur-ci fait 64. Les deux menus s'ouvrent ainsi à la même
  abscisse.
- MobileScaffold n'ayant aucun porteur en haut, l'IPTV active s'affiche
  en sous-titre de l'entrée « Changer de Playlist », dans les réglages
  mobile et desktop.

Vérifié par un agent distinct, après calibration sur une copie portant
une différence plantée. Aucune erreur de compilation relevée à la
lecture ; rien n'est rendu, donc tailles réelles, débordement et
ouverture effective du menu restent non évalués. Le SDK Flutter de cette
machine (3.10.3) est antérieur au ^3.13.0 exigé par le projet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-19 14:39:09 +02:00
MichaelandClaude Opus 5 52504d4e1a Multi-IPTV : le quota du navigateur ne doit plus écraser le choix d'IPTV
Console du navigateur, à deux playlists :

  QuotaExceededError: Failed to execute 'setItem' on 'Storage':
  Setting the value of 'xtream_cache_<id>_live_channels' exceeded the quota.

Les catalogues mis en cache (chaînes, films, séries), plusieurs Mo par
IPTV, saturent le stockage. L'erreur était avalée, mais le stockage
restait plein : toute écriture suivante échouait, dont celle de
localStorage['selected_playlist']. L'entrée gardait l'IPTV précédente,
AuthedHttp envoyait un X-Playlist-Id périmé, le serveur résolvait la même
playlist pour les deux, et Auto NBA n'avait qu'une règle commune —
désactivée d'un côté, désactivée de l'autre.

- persistSelectedPlaylist purge les catalogues et réessaye avant de
  renoncer ; elle ne lève plus jamais, sans quoi le clic de sélection
  n'aurait plus posé l'état du tout.
- _saveToLocalCache purge les catalogues des autres IPTV puis réessaye :
  un catalogue se recharge depuis le serveur, le choix d'IPTV ne se
  devine pas.

Non vérifié localement : le SDK Flutter de cette machine (3.10.3) est
antérieur au ^3.13.0 exigé par le projet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-19 14:15:19 +02:00
MichaelandClaude Opus 5 5169d0843a Multi-IPTV : persister l'IPTV choisie dès le clic
Déconnecter Auto NBA sur une IPTV la déconnectait sur l'autre : les deux
tombaient en fait sur la même règle. L'en-tête X-Playlist-Id était absent,
donc le serveur repliait sur la playlist la plus récente, pour la lecture
comme pour l'écriture.

AuthedHttp lit localStorage['selected_playlist'], écrit jusqu'ici par le
seul ref.listen de playlistPersistenceProvider. Or ce listener n'existe
qu'une fois activeXtreamServiceProvider lu, c'est-à-dire une fois le
tableau de bord construit : le premier choix d'une session changeait
l'état avant que quiconque n'écoute, et n'était jamais écrit.

- persistSelectedPlaylist écrit l'entrée ; le listener l'appelle.
- selectPlaylist persiste puis pose l'état ; les deux écrans de
  sélection (web et mobile) passent par lui.

Non vérifié localement : le SDK Flutter de cette machine (3.10.3) est
antérieur au ^3.13.0 exigé par le projet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-10-19 14:11:19 +02:00
MichaelandClaude Opus 5.5 4f978755fd Multi-IPTV : chaque fonction utilise la playlist sélectionnée
Le serveur ignorait l'IPTV choisie : direct, VOD, guide, enregistrements,
season passes et Auto NBA passaient tous par la playlist la plus récente
(ou celle du premier utilisateur pour la capture FFmpeg), et les
enregistrements s'affichaient sous toutes les playlists.

- get_playlist respecte ?playlist=<id> (prioritaire) puis l'en-tête
  X-Playlist-Id, avec contrôle du propriétaire (admin : toutes).
- Sessions FFmpeg HLS/VOD séparées par IPTV (même stream_id possible) ;
  le m3u8 propage ?playlist= aux segments, redirections et bascule
  .ts → HLS du lecteur web aussi.
- tv_recordings, season_passes, auto_rules : colonne playlist_id
  (migrations v4-v6) ; lignes existantes rattachées à la playlist la
  plus récente de leur propriétaire ; URL de capture avec ?playlist= ;
  listes filtrées par IPTV ; une règle NBA par IPTV.
- Proxy /api/xtream : cache d'allowlist par playlist, paramètre retiré
  avant l'appel au panneau ; CORS autorise X-Playlist-Id.
- Client : AuthedHttp envoie X-Playlist-Id ; XtreamService ajoute
  ?playlist= aux flux, à l'EPG et aux appels xtream-api (cache distinct).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 19:53:45 +02:00
MichaelandClaude Opus 5.5 29b4e11d13 Auto NBA : afficher les matchs beIN même sans chaîne reliée, diagnostic
L'onglet restait vide alors que le guide beIN annonçait des matchs : une
diffusion n'était prise en compte que si sa chaîne beIN était reliée à
un flux de la règle (liste vide si la détection avait échoué à la
création, ou nom différent chez le revendeur).

- Toute diffusion en direct du guide beIN est rapprochée et affichée ;
  seul l'enregistrement exige un flux IPTV (sinon « absente de la
  playlist », bouton inactif, aucun enregistrement créé).
- Les beIN FR de la playlist sont redétectées à chaque passage et
  complètent les flux choisis dans la règle.
- Plus besoin de playlist pour afficher le programme.
- Diagnostic par règle (source, directs au guide, chaînes reliées ou
  absentes, matchs rapprochés) renvoyé par l'API et affiché quand la
  liste est vide.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 17:16:01 +02:00
MichaelandClaude Opus 5.5 5f0f6abcc6 Auto NBA : guide officiel beIN, choix manuel, un seul enregistrement par créneau
- Programme officiel beIN Sports France (api/opta, ~8 jours) comme
  source : chaîne exacte et direct/rediffusion explicites, recoupés avec
  le coup d'envoi ESPN (un faux « direct » du guide est écarté). Repli sur
  l'EPG IPTV si le site est injoignable. Seuls les matchs annoncés en
  direct sur beIN sont affichés.
- Plus aucun enregistrement automatique : seul un « Enregistrer »
  explicite programme un match ; les programmations sans choix sont
  annulées.
- Un seul flux IPTV : un match qui chevauche un match choisi ou un autre
  enregistrement est refusé (409) et affiché « Créneau pris » ; la liste
  est relue après chaque choix et les boutons sont figés pendant la
  requête.
- Interrupteurs lisibles (curseur et piste distincts), boutons
  Enregistrer / Choisi à la place des interrupteurs par match.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 16:33:08 +02:00
MichaelandClaude Opus 5.5 f291b66087 Enregistrements : auto-enregistrement des matchs NBA en direct sur beIN Sports FR
Le calendrier NBA (API ESPN, 21 jours) annonce les matchs à l'avance ;
l'EPG des beIN Sports françaises confirme la chaîne ~48 h avant. Seule la
diffusion qui démarre avec le coup d'envoi est retenue : les rediffusions
sont ignorées, sans dépendre des titres. Le CDN nba.com refuse les
requêtes serveur (403), d'où ESPN.

- Serveur : tables auto_rules / nba_games (migration v3 : source_rule_id),
  réconciliation toutes les 30 min, marges, filtre d'équipes, conflits de
  créneaux, rétention, API /api/auto-rules (oui/non par match).
- Détection des beIN FR (hors MENA, TR, ES…), meilleure qualité par chaîne.
- XMLTV : sous-titre, catégories, drapeaux live/new/premiere/
  previously-shown, horizon EPG_HORIZON_HOURS.
- Plex/Jellyfin : vignette et affiche avec logos des équipes, .nfo,
  pochette intégrée au MKV, nom de fichier lisible.
- UI : onglet « Auto NBA » et pastille sur les enregistrements.
- Docker : fonts-dejavu-core (texte des vignettes) et tzdata.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-18 11:31:23 +02:00
MichaelandClaude Opus 5.5 fdf98f9659 Films : demander le fichier sous sa vraie extension
Le serveur réclamait toujours <id>.mkv au panneau. Un film stocké en .mp4
était refusé (HTTP 551 constaté en prod). Le client transmet désormais
container_extension (?ext=), validé côté serveur ; mkv reste le défaut
pour un client qui ne l'envoie pas. Même approche que xtremobile, qui lit
ces films sans souci.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-10 12:09:11 +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
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 8bed6cf7b8 Merge remote-tracking branch 'origin/main' into claude/fonctionnalites-frontend
# Conflicts:
#	CHANGELOG.md
2026-08-28 06:48:20 +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 aacfa54604 Rebranche les fonctionnalités codées mais inaccessibles
Onglet Enregistrements :
- TabBar à 3 vues : Guide TV (programmer depuis l'EPG), liste des
  enregistrements, Season Passes. _EpgGuideView et _SeasonPassesView
  (~800 lignes fonctionnelles) n'étaient plus instanciés depuis une
  refonte — programmer depuis le guide et les enregistrements
  récurrents étaient devenus inaccessibles
- Confirmation avant suppression d'un enregistrement (la corbeille
  supprimait immédiatement, sans retour ni annulation)
- Erreur de chargement avec bouton Réessayer

Favoris :
- Bouton cœur sur les tuiles chaînes desktop (overlay) et mobile :
  toggleFavorite() n'était appelé nulle part, le filtre « Favoris »
  affichait toujours une liste vide

Reprise de lecture :
- Films et épisodes lisent playbackPositionsProvider et passent
  startTime au player : les positions étaient écrites mais jamais
  relues, tout repartait de zéro
- Plus de sauvegarde de position en live (une entrée bidon par chaîne
  zappée)

Mobile :
- 5e onglet REC réutilisant RecordingsTab
- IndexedStack au lieu du switch : les onglets conservent scroll et
  catalogues chargés (aligné sur le dashboard desktop)

Dashboard :
- Menu profil sur l'avatar de la sidebar (nom d'utilisateur +
  déconnexion) : le bouton était mort, aucune déconnexion possible
  depuis le dashboard desktop
- Erreur de chargement des chaînes avec bouton Réessayer

Validé : flutter analyze (202 issues, identique à la baseline main,
0 erreur/warning), flutter test (OK), flutter build web --release (OK).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015oEu9QayWsw7hCKhenxgVa
2026-08-27 12:29:23 +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 488e354bf0 Rafraîchissement automatique de la page Enregistrements
- Nouveau bus de notification (recordingsRefreshBus) : la liste se recharge
  immédiatement dès qu'un enregistrement est créé, planifié ou arrêté depuis
  n'importe où dans l'app (guide EPG, modal, widget rapide)
- Polling en arrière-plan : 5 s quand un enregistrement est en cours ou
  planifié, 20 s sinon, pour suivre les changements de statut sans clic
- Rafraîchissement silencieux (pas de spinner ni de vidage de liste) pour
  éviter le clignotement ; les erreurs transitoires ne remplacent pas la liste
- Indicateur « Suivi auto » avec l'heure de dernière actualisation

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015oEu9QayWsw7hCKhenxgVa
2026-08-27 11:15:48 +00:00
MichaelandClaude Opus 5 5ab7203c8d fix(epg): cache client du guide et plafond de débit relevé
Les journaux du serveur montrent la grille Live TV se faire couper en 429 :
"Rate limit exceeded", une trentaine de requêtes refusées d'affilée après
quelques écrans de défilement.

Deux causes cumulées. D'une part le passage du guide de Dio vers AuthedHttp a
fait perdre le cache de réponses qui existait auparavant : chaque
reconstruction de tuile repartait en requête. Un cache mémoire de dix minutes
le rétablit — le backend garde déjà ces réponses trente minutes, il n'y a
aucune raison d'aller les rechercher.

D'autre part le plafond de deux cents requêtes par minute et par adresse était
trop bas pour l'usage réel : une grille demande un logo et un guide par tuile,
et tous les clients partagent l'adresse du reverse proxy. Il passe à six
cents.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:17:55 +02: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 Opus 5 c3b28b8278 fix(player): le bouton plein écran ne faisait rien sous Chrome
La détection lisait `webkitFullscreenElement` et `mozFullScreenElement` via un
cast `dynamic`. dart2js ne retombe pas sur la propriété JavaScript quand le
membre n'est pas déclaré sur la classe `Document` : il lève un
`NoSuchMethodError`, visible en console sous la forme
`method not found: 'gaR5'`. Le test d'état échouait donc à chaque appel, avant
même de pouvoir demander le plein écran.

Seule l'API non préfixée est désormais utilisée ; elle est supportée par tous
les navigateurs visés.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 10:39:38 +02:00
MichaelandClaude Opus 5 f490e96448 feat(player): bouton plein écran et reconnexion du proxy live
Plein écran — le bouton n'existait que dans les contrôles VOD, sous une icône
aspect_ratio peu parlante, et les contrôles Live TV n'en avaient aucun. Ajout
du bouton côté Live TV et passage aux icônes fullscreen / fullscreen_exit.
L'état suit désormais l'évènement fullscreenchange du document : sortir par la
touche Échap laissait l'icône désynchronisée.

Proxy live — la route /api/live/<id>.ts relayait la source sans aucune reprise.
Les panneaux Xtream ferment régulièrement la connexion en cours de route, et le
MediaSource du navigateur recevait alors un sourceEnded : la lecture s'arrêtait
net au bout de quelques dizaines de secondes. Le proxy rouvre maintenant la
source tant que le client écoute, avec quatre tentatives et une seconde
d'attente entre chaque, le temps que le panneau libère le slot de connexion.
Le compteur repart de zéro dès qu'une connexion a tenu plus d'une minute.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 10:31:13 +02:00
MichaelandClaude Opus 5 8b82b101aa fix(ui): remplacer les couleurs Material codées en dur par les jetons Warm Cinema
41 couleurs Material brutes traînaient encore dans l'interface, hors palette :
rouge, vert, orange et surtout un violet omniprésent sur les Season Pass, qui
n'appartient à aucune famille du thème.

Correspondances retenues :
- Colors.red / redAccent -> AppColors.live (#E5484D) pour les badges LIVE et
  les indicateurs d'enregistrement, AppColors.error pour les messages d'erreur
- Colors.green / greenAccent -> AppColors.success
- Colors.orangeAccent -> AppColors.warning
- Colors.amber -> AppColors.ratingGold, pour aligner les étoiles de notation
  du mobile sur celles du bureau
- Colors.purpleAccent -> famille secondary (terre cuite)
- fonds de boutons passés sur les variantes *Container avec leur couleur de
  premier plan, le remplissage clair ne portant pas de texte lisible

Le titre du programme EPG dans la grille Live TV utilisait ratingGold, un jeton
réservé aux notes : il passe sur primary, l'accent ember du thème.

Les fonds de SnackBar vert/rouge deviennent surfaceContainerHigh, le succès et
l'échec restant portés par le pictogramme du message.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 10:16:38 +02:00
MichaelandClaude Opus 5 537312908a fix(epg,ui): utiliser get_simple_data_table pour l'EPG et recaler la sidebar sur le thème
EPG — les trois chemins (backend /api/epg, XtreamService.getShortEpg et
getShortEPG) n'utilisaient que get_epg et get_short_epg. Beaucoup de
panneaux Xtream ne connaissent pas get_epg : ils répondent 200 avec le
bloc d'authentification, sans epg_listings, ce qui produisait un guide
vide indiscernable d'une chaîne sans programme. get_short_epg y renvoie
epg_listings vide en permanence.

- backend : get_simple_data_table en premier, get_short_epg en repli
- client : bascule automatique et mémorisée vers le tableau complet dès
  qu'un panneau est muet sur l'action légère, pour ne pas doubler les
  requêtes sur chaque tuile de grille
- dates dérivées des *_timestamp epoch et émises en ISO-8601 UTC : les
  champs texte sont dans le fuseau du panneau, sans indicateur de zone,
  et étaient relus comme de l'heure locale — le guide était décalé et
  les enregistrements planifiés depuis le guide l'étaient aussi
- cache backend : la clé incluait seulement le channelId, deux playlists
  partageant un stream_id se servaient mutuellement leur guide

UI — les deux halos d'ambiance du dashboard étaient restés de l'ancien
thème : primary (#FFB68C) à 40 % posé sur le coin haut-gauche, donc pile
derrière la sidebar, et info (#A3B8C4, bleu-gris) à 35 % dans une palette
entièrement chaude. Ramenés à des braises ember à 8 % / 6 %.

- fond via AppColors.backgroundGradient
- onglet actif rempli en primaryContainer (surface) et non primary
  (teinte claire réservée au texte/icônes)
- sidebar en niveau flottant : en niveau 1 son fond #181310 se confondait
  avec le haut du dégradé #1A1310

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 09:53:17 +02:00
MichaelandClaude Opus 5 51e6d9d693 fix(epg): stop swallowing EPG failures, cap gateway concurrency
getShortEpg ended in `catch (e) { return []; }`, so a 401, a timeout, a
rate-limited panel and a genuinely empty guide all rendered the same
"No Info" label. That made the current problem undiagnosable from the
UI. Failures are now logged with the HTTP status, the Dio error type and
the response shape.

Two concrete causes are handled while we are here:

- Some Xtream panels answer player_api.php with a textual Content-Type.
  Dio then hands back a raw String, and `response.data['epg_listings']`
  threw, landing in the silent catch. The body is now parsed explicitly
  when it arrives as a String.
- The channel grid mounts one EPG provider per visible tile, firing
  ~32 concurrent requests at player_api.php. Many panels rate-limit well
  below that and fail the whole batch. A 3-slot semaphore serialises
  them in small groups.

This does not by itself prove why the guide is empty in production; it
makes the reason visible in the browser console.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 22:14:47 +02:00
MichaelandClaude Opus 5 ef744c215e fix(ui): make screen backgrounds full-bleed instead of content-sized
The gradient Container in the playlist selection and admin screens sized
itself to its content rather than the viewport: under the loose body
constraints, SingleChildScrollView and Wrap both shrink-wrap, so the
gradient was painted only across a 436 px band (48 padding + 340 card +
48 padding) with the rest of the screen falling through to bare black.
BoxConstraints.expand() forces full coverage.

Also:
- both screens now use AppColors.backgroundGradient. The previous ramp
  ran surfaceContainerLowest -> background -> baseLevel0, i.e. dark to
  lighter to dark, which put a pale band across the middle
- the playlist card was a 3% cream translucent plate, so it took on
  whatever sat behind it instead of resting on it; it is now an opaque
  surface (surfaceContainerLow, surfaceContainerHigh on hover) with the
  lift() shadow at rest and the ember focus halo on hover
- Scaffold backgroundColor pinned to baseLevel0 so route transitions
  never flash pure black

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 21:57:35 +02:00
MichaelandClaude Opus 5 ad3e970c8d fix(api): send session token on EPG, recordings and season-pass calls
The security hardening in 60d3f42 moved /api/epg, /api/recordings and
/api/season-passes behind authMiddleware, but the Flutter client still
called them through bare package:http. Those requests carry no
Authorization header, and on web BrowserClient sets withCredentials to
false so the session cookie is not sent either — every call came back
401. Symptoms: empty TV guide, empty recordings list, empty season
passes.

Adds AuthedHttp, a thin wrapper that injects the same token ApiClient
and XtreamService already use (localStorage['auth_token']), and routes
the 13 affected calls through it.

subtitle_service is left on plain http: it fetches third-party subtitle
URLs, not our API, and must not leak the session token.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 21:54:01 +02:00
MichaelandClaude Opus 5 1feeaae78a feat(design): Warm Cinema theme + event-driven adaptive players
Replaces the Projector Noir palette with "Warm Cinema" and rebuilds the
web players around a shared, event-driven engine.

Design
- app_colors: warm charcoal (#0B0908), cream (#F3E9DF), ember (#D9541F);
  adds posterScrim/heroScrim gradients, lift()/emberFocus() shadows,
  const cream-alpha tokens replacing Colors.whiteNN, and primaryFill,
  a deeper ember reserved for text-bearing fills so white labels clear
  the 4.5:1 AA threshold that #D9541F alone does not
- app_theme / mobile_theme: Fraunces (display serif) + Karla (body),
  tighter radii, 20+ component themes
- glass_container: opaque layered surfaces replace the blurred glass
- themed_loading_screen restyled; players and boot splash follow

Performance
- drop every BackdropFilter: backdrop blur forced a full framebuffer
  read per frame per card, the main source of scroll jank
- bundle Fraunces/Karla as assets so google_fonts stops fetching from
  fonts.gstatic.com at startup
- bound image decode size on 12 sites: a 40-poster grid drops from
  ~240 MB to a few tens of MB of GPU memory

Players (new shared engine: web/xf-player-core.js)
- start on media events instead of polling the buffer every 100-200 ms
- hls.js/mpegts.js loaded on demand, so ~213 KB of mpegts is never
  fetched for HLS streams
- liveSyncDurationCount 10 -> 2, startFragPrefetch on, testBandwidth
  off, MPEG-TS initial stash 512 KB -> 64 KB
- adaptive escalation fast -> balanced -> safe after repeated stalls in
  a 60 s window, applied live for HLS so there is no visible cut

Preserved from the recent streaming work on main: server-side quality
selection, stable player viewId, the turbo direct MPEG-TS path, vendored
library cache headers and the CI/Docker changes. Only the theme layer and
the player front-end were replaced; GlassCard stays in ui_components.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-10 21:42:56 +02:00
MichaelandClaude Fable 5 e332895d7d feat(design): Projector Noir theme - tungsten amber on film-black
Full visual refonte replacing the generic neon-blue glass look:

- palette: warm film-black surface ladder, tungsten amber primary,
  warm taupe secondary, verdigris teal tertiary; semantic and
  category colors retuned to match (coral live, gold movies)
- typography: Syne (display/headlines) + Instrument Sans (body/UI)
  replace Space Grotesk/Inter/Outfit across all screens
- glassmorphism tokens warmed (amber inner glow, warm-white borders)
- web player theme.css + index.html loader mirror the new tokens
- hardcoded Colors.blue* in streaming settings and recordings tabs
  now use AppColors tokens
- DESIGN_SYSTEM.md updated to v3.0 Projector Noir

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 14:52:45 +02:00
MichaelandClaude Fable 5 969d40dec5 fix(ci): exclude bin/ from root analyzer and clean remaining warnings
The frontend CI job ran `flutter analyze` from the repo root, which also
analyzed the bin/ server package without its dependencies resolved
(shelf_router, sqlite3, bcrypt, test), producing hundreds of
uri_does_not_exist errors. bin/ is a standalone package covered by the
backend job, so it is now excluded from root analysis.

Also:
- Remove all unused fields/variables flagged as analyzer warnings
  (api_client, cache_service, player_screen, subtitle_service,
  live_tv_tab, mobile screens)
- Run `flutter analyze --no-fatal-infos` in CI: pre-existing deprecation
  infos (withOpacity, dart:html) stay non-fatal while errors and warnings
  still fail the build

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 10:17:40 +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
MichaelandClaude Opus 4.7 b985f11704 fix: resolve all compile errors from Stitch theme merge
- Add missing AppTheme animation constants (durationSm/Md/Lg/Xl, curveSmooth)
  to satisfy remote widget references.
- Replace deprecated GlassContainer(opacity:) with .glass()/.floating()/.base()
  in login_screen, live_tv_tab, mobile_live_tv_tab, player_screen,
  dashboard_screen.
- Fix const-eval errors by removing const from BorderSide/Text/BoxDecoration
  using AppColors.focusColor and AppColors.border (runtime getters).
- Fix subtitle_service.dart: padEnd → padRight (Dart String API).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 07:37:37 +02:00
MichaelandClaude Opus 4.7 60228f5576 fix: resolve GlassContainer API mismatch and broken remote files
- Replace deprecated GlassContainer(opacity:/hasBorder:) calls in
  dashboard_screen.dart and player_screen.dart with .glass() constructor.
- Fix epg_grid_screen.dart missing provider import and Playlist type mismatch.
- Delete broken/unreferenced files: network_service.dart, epg_grid_screen.dart.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-28 07:24:41 +02:00
MichaelandClaude Opus 4.7 2ba16caf55 Merge remote-tracking branch 'origin/main' into main
Resolved conflicts by keeping remote functional features (RecordingScheduler,
FFmpeg HLS transcoding, mobile player, channel cards) and restoring local
Stitch theme foundation (app_colors, app_theme, glass_container, mobile_theme).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 22:17:52 +02:00
MichaelandClaude Opus 4.7 f7eaa724e2 feat(theme): complete redesign to Cyber-Cinematic Glass (Stitch)
Replace the legacy Apple TV-style purple/cyan theme with the full
Google Stitch Cyber-Cinematic Glass design system across the
entire app (desktop + mobile).

- New Material 3 dark ColorScheme: background #121317, primary
  #adc6ff, primaryContainer #4b8eff, surface containers, etc.
- Typography: Space Grotesk (headlines) + Inter (body/labels)
- 3-level glassmorphism via GlassContainer: base, glass, floating
- Primary gradient: #007AFF → #00C6FF with blue glow effects
- Removed old compatibility aliases (focusColor, border, textPrimary,
  textSecondary); all code now uses semantic Stitch tokens
- Systematically replaced all Colors.white/black/red/grey and
  GoogleFonts.roboto/outfit with Stitch equivalents
- All 36 lib/ files updated, zero compilation errors

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-04-27 22:06:29 +02:00
Michael e641d04a3a feat: implement streaming handler with FFmpeg-based live and VOD HLS transcoding support 2026-04-01 21:35:25 +02:00
Michael f08fccc0d1 feat: implement HLS streaming handler with FFmpeg transcoding and add IPTV player screen UI 2026-04-01 21:28:47 +02:00
Michael d82f9f6846 feat: add RecordingsTab widget and EPG guide view for IPTV playback management 2026-04-01 21:21:26 +02:00
Michael e4301077b0 feat: implement HLS live streaming and VOD transcoding handler with FFmpeg and NVIDIA GPU support 2026-04-01 21:14:33 +02:00
Michael 5e7e86e4d8 feat: add RecordingsTab widget with EPG guide and channel navigation for IPTV playlists 2026-04-01 20:58:42 +02:00
MichaelandClaude Haiku 4.5 6efdadd6fd fix: simplify RecordingsTab to fix null check operator error
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
2026-04-01 20:45:48 +02:00
Michael 0049a7c596 feat: add RecordingsTab widget and local settings configuration file 2026-04-01 20:41:27 +02:00
Michael 1e6d3741d1 feat: implement RecordingsTab and EPG guide view for IPTV feature 2026-04-01 20:37:02 +02:00
Michael 2a5eb08807 feat: add RecordingsTab widget and EPG guide view for IPTV playback 2026-04-01 20:31:52 +02:00
Michael cd859d643c feat: add RecordingsTab widget with integrated EPG guide functionality 2026-04-01 20:26:49 +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 77dc9053bb feat: implement mobile dashboard screen with live TV tab and channel filtering functionality 2026-03-29 12:46:32 +02:00
Michael ddb8110c40 feat: add mobile scaffold layout and dedicated mobile video player implementation 2026-03-29 12:07:14 +02:00
Michael fa6ae90d8d feat: add mobile-optimized IPTV player with HLS and MpegTS support 2026-03-29 10:19:36 +02:00
Michael 3c9a9c7be7 feat: introduce playlist selection screen allowing users to choose from available IPTV playlists. 2026-03-26 21:11:14 +01:00