Files
xtremflow/bin/utils/stream_pipe.dart
T
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

50 lines
1.4 KiB
Dart

import 'dart:async';
/// Relaie [source] vers le client en conservant la contre-pression.
///
/// POURQUOI : un `StreamController` nu ne transmet pas la pause de son
/// abonné à la source. Quand le navigateur lit plus lentement que FFmpeg ne
/// produit — réseau domestique, onglet en arrière-plan —, le serveur empilait
/// donc les paquets en mémoire sans jamais ralentir le producteur : la
/// consommation grimpe et la lecture dérive derrière le direct, ce qui se voit
/// à l'écran comme des saccades.
///
/// [onStop] est appelé une seule fois, quand le client se déconnecte ou que la
/// source se termine : c'est là qu'on tue le processus FFmpeg, sinon il en
/// reste un par zapping.
Stream<List<int>> pipeWithBackpressure(
Stream<List<int>> source, {
required void Function() onStop,
}) {
late final StreamController<List<int>> controller;
late final StreamSubscription<List<int>> subscription;
var stopped = false;
void stop() {
if (stopped) return;
stopped = true;
onStop();
}
controller = StreamController<List<int>>(
onPause: () => subscription.pause(),
onResume: () => subscription.resume(),
onCancel: () {
final cancelled = subscription.cancel();
stop();
return cancelled;
},
);
subscription = source.listen(
controller.add,
onError: controller.addError,
onDone: () {
controller.close();
stop();
},
);
return controller.stream;
}