Compare commits

...
Author SHA1 Message Date
MichaelandClaude Opus 5 d6c7184475 Lecture : un échec de chargement de mpegts.js n'est plus fatal
L'écran « Échec du chargement : vendor/mpegts.min.js » était un cul-de-sac :
la moindre requête ratée sur la lib condamnait la chaîne, alors que le flux
restait parfaitement lisible autrement.

- xf-player-core : le chargement des libs est retenté une fois en
  contournant le cache HTTP (les libs sont servies en max-age=86400, une
  entrée tronquée bloquait le lecteur jusqu'au vidage manuel du cache), et
  seul un succès est mémorisé — la promesse rejetée restait en cache et
  rendait tout réessai impossible pour le reste de la session.
- Repli automatique : si mpegts.js reste introuvable, le live bascule sur la
  route HLS équivalente (où FFmpeg réencode déjà l'audio en AAC) ; si hls.js
  reste introuvable, on tente le HLS natif. Plus d'écran d'erreur quand un
  chemin de lecture est encore disponible.
- Message d'erreur exploitable : la cause réelle est affichée (HTTP 404,
  429, réseau injoignable) au lieu d'un échec indifférencié.
- Préchargement corrigé : les trois players préchargeaient hls.js en dur,
  soit 618 Ko téléchargés pour rien à chaque zap TV — c'est mpegts.js qui
  sert au direct — au détriment du flux et du chargement de mpegts.js
  lui-même. Le préchargement suit désormais le flux demandé, avec la même
  décision que XFPlayer.start() (et ne charge rien sur Safari/iOS).
- Serveur : avertissement explicite au démarrage si web/vendor/*.min.js
  manque, au lieu de laisser le navigateur échouer sans explication.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014FSovdudi3KBwZK3aC42fw
2026-09-09 16:40:31 +00:00
LogiFlow 175cff560c Merge pull request #11 from R0m1k3/claude/lecture-video
Lecture vidéo : son garanti sur toutes les chaînes, zapping et démarrages accélérés
2026-08-28 09:01:05 +02:00
6 changed files with 183 additions and 9 deletions

No files matched your search

+3
View File
@@ -7,6 +7,9 @@
- **Players mis à jour** : hls.js 1.6.7 → 1.7.1, mpegts.js 1.7.3 → 1.8.2 (vendorisés)
- **Démarrage plus rapide** : sonde FFmpeg bornée (`-fflags nobuffer`, probesize réduit) sur le live et le turbo ; probesize VOD 10 Mo → 5 Mo ; preset live `high` et lecture d'enregistrement en `veryfast` (medium ne tenait pas le temps réel) ; détection de playlist toutes les 100 ms au lieu de 500 ms ; suppression du cache-buster qui re-téléchargeait player.html à chaque zap (les .html passent en no-cache serveur)
- **Latence live maîtrisée** : rattrapage du direct activé dans mpegts.js (profil rapide) — les micro-coupures ne font plus dériver la lecture derrière le direct
- **Fix « Échec du chargement : vendor/mpegts.min.js »** : un échec de chargement d'une lib de lecture n'affiche plus un écran d'erreur définitif. Le chargement est retenté une fois en contournant le cache HTTP (une entrée tronquée condamnait le lecteur jusqu'au vidage manuel du cache), le résultat n'est mémorisé qu'en cas de succès (une promesse rejetée en cache rendait tout réessai impossible) et, si la lib reste introuvable, le live bascule automatiquement sur la route HLS équivalente. Le message affiché précise désormais la cause (HTTP 404, 429, réseau injoignable)
- **Préchargement de la bonne lib** : les trois players préchargeaient hls.js en dur, soit 618 Ko téléchargés pour rien à chaque zap TV — où c'est mpegts.js qui sert — au détriment du flux et du chargement de mpegts.js. Le préchargement suit maintenant le flux réellement demandé (et ne charge rien sur Safari/iOS, qui lit le HLS nativement)
- **Alerte au démarrage** : le serveur signale explicitement l'absence de `web/vendor/*.min.js` au lancement, au lieu de laisser le navigateur échouer sans explication
### 🧰 Qualité / Infra
- **Builds reproductibles** : `pubspec.lock` (frontend et backend) désormais versionnés et utilisés par le Dockerfile — chaque build résolvait jusqu'ici des versions fraîches
+14
View File
@@ -259,6 +259,20 @@ void main(List<String> args) async {
}),
);
// Les libs de lecture sont servies depuis l'image, plus depuis un CDN : si
// elles manquent (build web incomplet, copie Docker partielle), le lecteur
// échoue côté navigateur avec un simple « Échec du chargement » sans que
// rien ne l'ait signalé au démarrage. On le dit ici, une fois, clairement.
for (final lib in const ['hls.min.js', 'mpegts.min.js']) {
final file = File('$webPath/vendor/$lib');
if (!file.existsSync()) {
print(
'[Server] ATTENTION: web/vendor/$lib absent de $webPath — '
'la lecture échouera dans le navigateur.',
);
}
}
// Create static handler
final baseStaticHandler = createStaticHandler(
webPath,
+29 -1
View File
@@ -11,7 +11,35 @@
et environ 200 Ko de JS jamais téléchargés dans le cas courant.
-->
<link rel="stylesheet" href="xf-player.css">
<link rel="preload" href="vendor/hls.min.js" as="script">
<script>
// Précharge la SEULE lib utile à CE flux, dès le parse du <head>.
// Un préchargement fixe de hls.js faisait télécharger 618 Ko inutiles
// à chaque zap TV — la lib réellement nécessaire au direct est
// mpegts.js — et cette bande passante était prise sur le flux
// lui-même et sur mpegts.js, jusqu'à faire échouer son chargement sur
// les liens contraints. Même décision que XFPlayer.start().
(function () {
var p = new URLSearchParams(location.search);
var url = (p.get('url') || '').toLowerCase();
var isHls = url.indexOf('.m3u8') !== -1;
var nativeHls =
document.createElement('video')
.canPlayType('application/vnd.apple.mpegurl') !== '';
var lib = null;
if (isHls) {
// Safari/iOS lit le HLS nativement : aucune lib à charger.
if (!nativeHls) lib = 'hls.min.js';
} else if (p.get('type') === 'live' || url.indexOf('.ts') !== -1) {
lib = 'mpegts.min.js';
}
if (!lib) return;
var l = document.createElement('link');
l.rel = 'preload';
l.as = 'script';
l.href = 'vendor/' + lib;
document.head.appendChild(l);
})();
</script>
</head>
<body>
+29 -1
View File
@@ -5,7 +5,35 @@
<meta charset="UTF-8">
<title>XtremFlow Player Lite</title>
<link rel="stylesheet" href="xf-player.css">
<link rel="preload" href="vendor/hls.min.js" as="script">
<script>
// Précharge la SEULE lib utile à CE flux, dès le parse du <head>.
// Un préchargement fixe de hls.js faisait télécharger 618 Ko inutiles
// à chaque zap TV — la lib réellement nécessaire au direct est
// mpegts.js — et cette bande passante était prise sur le flux
// lui-même et sur mpegts.js, jusqu'à faire échouer son chargement sur
// les liens contraints. Même décision que XFPlayer.start().
(function () {
var p = new URLSearchParams(location.search);
var url = (p.get('url') || '').toLowerCase();
var isHls = url.indexOf('.m3u8') !== -1;
var nativeHls =
document.createElement('video')
.canPlayType('application/vnd.apple.mpegurl') !== '';
var lib = null;
if (isHls) {
// Safari/iOS lit le HLS nativement : aucune lib à charger.
if (!nativeHls) lib = 'hls.min.js';
} else if (p.get('type') === 'live' || url.indexOf('.ts') !== -1) {
lib = 'mpegts.min.js';
}
if (!lib) return;
var l = document.createElement('link');
l.rel = 'preload';
l.as = 'script';
l.href = 'vendor/' + lib;
document.head.appendChild(l);
})();
</script>
</head>
<body>
+29 -1
View File
@@ -8,7 +8,35 @@
<meta name="apple-mobile-web-app-capable" content="yes">
<title>XtremFlow Player Mobile</title>
<link rel="stylesheet" href="xf-player.css">
<link rel="preload" href="vendor/hls.min.js" as="script">
<script>
// Précharge la SEULE lib utile à CE flux, dès le parse du <head>.
// Un préchargement fixe de hls.js faisait télécharger 618 Ko inutiles
// à chaque zap TV — la lib réellement nécessaire au direct est
// mpegts.js — et cette bande passante était prise sur le flux
// lui-même et sur mpegts.js, jusqu'à faire échouer son chargement sur
// les liens contraints. Même décision que XFPlayer.start().
(function () {
var p = new URLSearchParams(location.search);
var url = (p.get('url') || '').toLowerCase();
var isHls = url.indexOf('.m3u8') !== -1;
var nativeHls =
document.createElement('video')
.canPlayType('application/vnd.apple.mpegurl') !== '';
var lib = null;
if (isHls) {
// Safari/iOS lit le HLS nativement : aucune lib à charger.
if (!nativeHls) lib = 'hls.min.js';
} else if (p.get('type') === 'live' || url.indexOf('.ts') !== -1) {
lib = 'mpegts.min.js';
}
if (!lib) return;
var l = document.createElement('link');
l.rel = 'preload';
l.as = 'script';
l.href = 'vendor/' + lib;
document.head.appendChild(l);
})();
</script>
</head>
<body>
+79 -6
View File
@@ -60,17 +60,67 @@
// ------------------------------------------------------------------
var _scriptCache = {};
function loadScript(src) {
if (_scriptCache[src]) return _scriptCache[src];
_scriptCache[src] = new Promise(function (resolve, reject) {
function _delay(ms) {
return new Promise(function (resolve) { setTimeout(resolve, ms); });
}
function _injectScript(src) {
return new Promise(function (resolve, reject) {
var s = document.createElement('script');
s.src = src;
s.async = true;
s.onload = resolve;
s.onerror = function () { reject(new Error('Échec du chargement : ' + src)); };
s.onload = function () { resolve(); };
s.onerror = function () {
// Laisser une balise morte dans le <head> ferait échouer une
// éventuelle nouvelle tentative sur le même src.
if (s.parentNode) s.parentNode.removeChild(s);
reject(new Error('script error'));
};
document.head.appendChild(s);
});
return _scriptCache[src];
}
/**
* Précise la cause d'un échec pour le message affiché et les logs :
* 404 = lib absente de l'image déployée, 429 = quota du rate limiter,
* échec réseau = lien coupé. Sans cela, toutes les causes se
* ressemblaient à l'écran.
*/
function _describeFailure(src) {
if (typeof fetch !== 'function') return Promise.resolve('');
return fetch(src, { credentials: 'same-origin', cache: 'no-store' })
.then(function (r) { return r.ok ? '' : ' (HTTP ' + r.status + ')'; })
.catch(function () { return ' (réseau injoignable)'; });
}
/**
* Charge une lib vendorisée, avec une seconde tentative qui contourne le
* cache HTTP : les libs sont servies en `max-age=86400`, donc une entrée
* tronquée par une coupure réseau condamnait le lecteur jusqu'au vidage
* manuel du cache navigateur. Seuls les succès sont mémorisés — une
* promesse rejetée gardée en cache rendait tout réessai impossible pour
* le reste de la session.
*/
function loadScript(src) {
if (_scriptCache[src]) return _scriptCache[src];
var attempt = _injectScript(src)
.catch(function () {
return _delay(400).then(function () {
return _injectScript(
src + (src.indexOf('?') === -1 ? '?' : '&') + 'reload=' + Date.now()
);
});
})
.catch(function () {
return _describeFailure(src).then(function (why) {
throw new Error('Échec du chargement : ' + src + why);
});
});
_scriptCache[src] = attempt;
attempt.catch(function () { delete _scriptCache[src]; });
return attempt;
}
/** Ouvre la connexion vers l'hôte du flux avant même de connaître l'URL finale. */
@@ -253,6 +303,11 @@
}
});
}).catch(function (err) {
// hls.js indisponible : plutôt que d'échouer, tenter les chemins qui
// ne demandent aucune lib (HLS natif Safari/iOS, sinon lecture directe
// si le navigateur sait décoder le conteneur).
self.log(err.message);
if (self._canPlayNativeHls()) { self._playDirect(self.url); return; }
self.onError(err.message);
});
};
@@ -267,6 +322,24 @@
}
self._createMpegts(isLive);
}).catch(function (err) {
// mpegts.js indisponible (build incomplet, coupure réseau, quota du
// rate limiter…) : le même flux reste lisible par la route HLS, où
// FFmpeg réencode déjà l'audio en AAC. Une seconde de latence en plus
// vaut mieux qu'un écran d'erreur sur une chaîne parfaitement lisible.
var hlsUrl = self._hlsEquivalent(self.url);
self.log(err.message + (hlsUrl ? ' — bascule sur le chemin HLS' : ''));
if (hlsUrl) {
self.url = hlsUrl;
self.onLoading('Ouverture du flux');
if (self._canPlayNativeHls()) { self._playDirect(hlsUrl); return; }
return self._startHls();
}
// Aucun équivalent HLS (flux direct fourni par la playlist) : dernier
// recours, laisser le navigateur décoder lui-même le conteneur.
if (self.video.canPlayType('video/mp2t') !== '') {
self._playDirect(self.url);
return;
}
self.onError(err.message);
});
};