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
This commit is contained in:
MichaelandClaude Opus 5 committed 2026-09-09 16:40:31 +00:00
1 parent 175cff560c
commit d6c7184475
6 files changed
+183 -9

No files matched your search

+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);
});
};