mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-11 17:30:00 +02:00
La lecture d'un enregistrement démarrait au milieu du programme et se coupait aussitôt ; il fallait relancer une deuxième fois pour qu'elle tienne. Cause principale, introduite avec le passage de la playlist en `EVENT` : tant que le transcodage n'est pas terminé, la playlist n'a pas d'`EXT-X-ENDLIST`, et hls.js traite alors tout contenu comme du direct. Sans `startPosition` explicite il démarre au « bord du direct » — donc collé au front d'encodage, sans la moindre avance de segments. D'où un départ en plein milieu, puis une coupure immédiate. Au second essai, le transcodage était terminé (ENDLIST présent, contenu traité en VOD) et tout se passait bien. Hors direct : `startPosition: 0` (ou la position de reprise), rattrapage de latence neutralisé (il accélérait la lecture puis forçait un saut en avant vers un direct inexistant), et délai d'attente de playlist porté de 10 s à 45 s — une session FFmpeg qui démarre n'est plus prise pour un manifeste mort. Cause de fond : la lecture ré-encodait la vidéo. Or la capture se fait en `-c copy`, donc le fichier contient le codec de la chaîne, presque toujours du H.264, directement lisible en HLS. Le ré-encodage tenait à peine le temps réel en 1080p, ce qui laissait le lecteur courir après l'encodeur en permanence. La vidéo est désormais copiée telle quelle quand elle est en H.264 (seul l'audio est converti en AAC) : la segmentation va à la vitesse du disque, l'enregistrement devient navigable en quelques secondes et les sauts ne redémarrent presque plus jamais FFmpeg. Les codecs que le navigateur ne sait pas lire (HEVC, MPEG-2…) restent ré-encodés. En complément : - le serveur attend trois segments d'avance avant de servir la playlist, et sert ce qu'il a plutôt qu'une erreur si le délai expire ; - le lecteur de bureau réessaie seul jusqu'à 3 fois, comme le lecteur mobile le faisait déjà ; - `XFPlayer.destroy()` retire ses écouteurs : sans ça, une relance laissait l'instance précédente réagir aux commandes du parent et republier des positions périmées ; - les appels ffprobe passent par `MediaProbe`, qui mémorise durée et codec tant que le fichier ne change pas. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01T1A6WRnoktWcT7ZG4b2rbC
158 lines
6.1 KiB
HTML
158 lines
6.1 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="fr">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<title>XtremFlow Player</title>
|
|
<!--
|
|
Les libs hls.js / mpegts.js ne sont PLUS chargées depuis un CDN ni de
|
|
façon bloquante : xf-player-core.js ne récupère que celle réellement
|
|
nécessaire au flux courant, en local. Deux requêtes bloquantes en moins
|
|
et environ 200 Ko de JS jamais téléchargés dans le cas courant.
|
|
-->
|
|
<link rel="stylesheet" href="xf-player.css">
|
|
<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>
|
|
<div id="player-container">
|
|
<video id="video" autoplay playsinline webkit-playsinline preload="auto"></video>
|
|
|
|
<div id="play-overlay">
|
|
<svg viewBox="0 0 24 24">
|
|
<path d="M8 5v14l11-7z" />
|
|
</svg>
|
|
</div>
|
|
|
|
<div id="loading" class="xf-center">
|
|
<div class="xf-leader">
|
|
<div class="xf-leader-cross"></div>
|
|
</div>
|
|
<div class="xf-label" id="loading-text">Ouverture du flux</div>
|
|
<div class="xf-hint" id="loading-hint"></div>
|
|
</div>
|
|
|
|
<div id="error" class="xf-center">
|
|
<div class="xf-error-mark">!</div>
|
|
<div class="xf-error-title" id="error-message">Erreur de chargement</div>
|
|
<div class="xf-error-sub">Vérifiez le lien ou votre connexion</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="xf-player-core.js"></script>
|
|
<script>
|
|
(function () {
|
|
var video = document.getElementById('video');
|
|
var loading = document.getElementById('loading');
|
|
var loadingText = document.getElementById('loading-text');
|
|
var loadingHint = document.getElementById('loading-hint');
|
|
var errorBox = document.getElementById('error');
|
|
var errorMessage = document.getElementById('error-message');
|
|
var playOverlay = document.getElementById('play-overlay');
|
|
|
|
function showLoading(msg) {
|
|
loading.style.display = 'block';
|
|
loadingText.textContent = msg || 'Ouverture du flux';
|
|
errorBox.style.display = 'none';
|
|
}
|
|
|
|
function hideLoading() {
|
|
loading.style.display = 'none';
|
|
loadingHint.textContent = '';
|
|
}
|
|
|
|
function showError(msg) {
|
|
hideLoading();
|
|
errorBox.style.display = 'block';
|
|
errorMessage.textContent = msg || 'Erreur de chargement';
|
|
playOverlay.style.display = 'none';
|
|
}
|
|
|
|
// Un enregistrement ou un film démarre une session FFmpeg côté
|
|
// serveur : le premier essai peut tomber pendant que l'encodeur
|
|
// se met en route. Le player mobile réessayait déjà seul ; ici
|
|
// l'utilisateur devait relancer la lecture à la main.
|
|
var retryCount = 0;
|
|
var maxRetries = 3;
|
|
var player = null;
|
|
|
|
function buildPlayer() {
|
|
if (player) { try { player.destroy(); } catch (e) {} }
|
|
|
|
player = new XFPlayer({
|
|
video: video,
|
|
logPrefix: '[Player]',
|
|
onLoading: showLoading,
|
|
onReady: function () {
|
|
hideLoading();
|
|
retryCount = 0;
|
|
playOverlay.style.display = 'none';
|
|
},
|
|
onError: function (msg) {
|
|
if (retryCount < maxRetries) {
|
|
retryCount++;
|
|
showLoading(
|
|
'Préparation du flux ' + retryCount + '/' + maxRetries
|
|
);
|
|
setTimeout(buildPlayer, 1500);
|
|
} else {
|
|
showError(msg);
|
|
}
|
|
},
|
|
onBlocked: function () {
|
|
hideLoading();
|
|
playOverlay.style.display = 'flex';
|
|
},
|
|
onProfileChange: function (name) {
|
|
// Retour visuel discret : l'utilisateur comprend pourquoi
|
|
// la latence augmente légèrement après des coupures.
|
|
loadingHint.textContent =
|
|
name === 'safe' ? 'Réseau instable — buffer maximal'
|
|
: 'Réseau instable — buffer élargi';
|
|
}
|
|
});
|
|
|
|
player.start();
|
|
window.xfPlayer = player;
|
|
}
|
|
|
|
playOverlay.addEventListener('click', function () {
|
|
video.play();
|
|
playOverlay.style.display = 'none';
|
|
});
|
|
|
|
buildPlayer();
|
|
})();
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|