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>
This commit is contained in:
MichaelandClaude Opus 5.5 committed 2026-10-10 11:51:56 +02:00
1 parent a9b43ff581
commit 662e1034e5
4 files changed
+329 -7

No files matched your search

+12 -7
View File
@@ -74,6 +74,17 @@ class FfmpegSessionManager {
bool contains(String id) => _sessions.containsKey(id);
/// Vrai si [getOrStart] lancerait un nouveau processus FFmpeg pour [id] —
/// donc une nouvelle connexion au fournisseur.
bool needsStart(String id, {required bool isLive}) {
final existing = _sessions[id];
if (existing == null) return true;
if (!existing.exited) return false;
// A VOD/recording transcode that finished cleanly is still fully
// playable from its segments — reuse it instead of re-transcoding.
return isLive || existing.exitCode != 0 || !_playlistComplete(existing);
}
/// Returns the existing healthy session or starts a new FFmpeg process.
///
/// [argsBuilder] receives the session working directory and returns the
@@ -86,17 +97,11 @@ class FfmpegSessionManager {
required List<String> Function(Directory dir) argsBuilder,
}) async {
final existing = _sessions[id];
if (existing != null && !existing.exited) {
if (existing != null && !needsStart(id, isLive: isLive)) {
existing.touch();
return existing;
}
if (existing != null) {
// A VOD/recording transcode that finished cleanly is still fully
// playable from its segments — reuse it instead of re-transcoding.
if (!isLive && existing.exitCode == 0 && _playlistComplete(existing)) {
existing.touch();
return existing;
}
// Process died: clean up before restarting
killSession(id);
}