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
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
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>
- 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>
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>