HLS s’arrête en arrière-plan ou au verrouillage : diagnostiquer la reprise
Diagnostiquez une lecture HLS qui se met en pause, prend du retard ou ne reprend pas après un changement d’onglet, d’application, le verrouillage du téléphone ou la suspension d’une page.
Un lecteur HLS peut fonctionner normalement jusqu’au changement d’onglet, au passage vers une autre application, au verrouillage du téléphone ou à une période d’inactivité. Au retour, l’image peut être figée, le son absent, la lecture très en retard sur le direct, ou la requête suivante de playlist peut échouer. Ces symptômes se ressemblent, mais leurs causes peuvent relever du code de l’application, du cycle de vie du navigateur, des politiques média du système, de l’expiration de la fenêtre live, de l’autorisation ou de la chaîne média.
Ne promettez pas qu’une page Web continuera la vidéo lorsqu’elle est masquée sur tous les appareils. Traitez la lecture continue en arrière-plan et la reprise fiable au premier plan comme deux exigences produit distinctes. Testez uniquement des flux que vous possédez ou êtes autorisé à inspecter. Supprimez des journaux partagés les URL signées, cookies, jetons, identifiants d’appareil, adresses IP et noms d’hôte privés.
Méthode de vérification — 24 septembre 2026 : nous avons exécuté un test déterministe de politique de reprise sous Node.js 24.12.0. La fenêtre live synthétique est passée du temps média 40–70 à 58–88. La position mémorisée 42 avait expiré : le test a donc choisi la position de synchronisation live déclarée à 76 ; la position 62 restait recherchable et a été conservée. Une position VOD à 120 dans une plage 0–300 a été conservée, une réponse HTTP 403 a conduit à une réautorisation plutôt qu’à des répétitions de requêtes média, et wasDiscarded: true a choisi une réinitialisation du live. Cela vérifie uniquement les calculs de plages et les décisions du test. Cela ne teste ni le cycle de vie d’un navigateur, ni un lecteur HLS, un décodage média, un réseau, l’écran verrouillé d’un téléphone, les politiques de veille d’un système ou un appareil physique. Le script est scripts/test-hls-background-recovery.cjs.
Nommez précisément la transition testée
« Ça s’arrête en arrière-plan » n’est pas un rapport reproductible. Avant toute modification, notez la transition exacte et le comportement attendu.
| Transition | Ce que la page peut observer | Ce que cela ne prouve pas |
|---|---|---|
| Un autre onglet devient actif | visibilitychange et document.visibilityState === "hidden" | Que la page a été figée, supprimée ou autorisée à jouer du son |
| Le navigateur ou l’application passe en arrière-plan | Le changement de visibilité peut être le dernier événement fiable | Qu’un événement ultérieur de type unload sera exécuté |
| L’écran se verrouille | Le document peut devenir masqué | Que chaque navigateur et système conserve la même politique média ou réseau |
| La page est gelée | Les tâches suspendables, dont beaucoup de minuteries, s’arrêtent | Que le moteur de rendu a été détruit |
| La page est supprimée | Elle est retirée pour économiser des ressources, puis rechargée | Que JavaScript a reçu un événement au moment de la suppression |
| Le composant est démonté ou la route change | L’application peut détruire le lecteur ou effacer sa source | Que le navigateur a imposé une politique d’arrière-plan |
La documentation MDN décrit visibilitychange lors du changement d’onglet, de la minimisation d’une fenêtre et du passage à une autre application sur mobile. Le guide Chrome sur le cycle de vie distingue les états masqué, gelé, terminé et supprimé ; sur mobile, le passage à l’état masqué est souvent le dernier état observable de façon fiable. Ce sont des signaux utiles, pas une garantie multiplateforme que le média continuera.
Capturez les preuves avant la disparition de la page
Ajoutez temporairement un journal de diagnostic dans une version contrôlée. Enregistrez le temps monotone, l’heure civile, la visibilité, l’état média, la position courante, toutes les plages recherchables et mises en tampon, ainsi que le dernier événement du lecteur ou du réseau. N’enregistrez pas les URL signées complètes.
function ranges(value) {
return Array.from(
{ length: value.length },
(_, index) => ({ start: value.start(index), end: value.end(index) })
);
}
function snapshot(video, event) {
console.info('media-lifecycle', {
event,
monotonicMs: performance.now(),
visibility: document.visibilityState,
paused: video.paused,
ended: video.ended,
currentTime: video.currentTime,
readyState: video.readyState,
networkState: video.networkState,
seekable: ranges(video.seekable),
buffered: ranges(video.buffered),
});
}
Écoutez aussi visibilitychange, pageshow, pagehide et, dans les navigateurs qui l’exposent, les événements de gel et de reprise. document.wasDiscarded peut aider à reconnaître certains chargements après suppression, mais l’absence de cette propriété ne prouve pas que la page n’a pas été interrompue. Pour play(), enregistrez la résolution ou le rejet de la promesse au lieu de supposer que l’appel a démarré la lecture.
Séparez le cycle de vie du document de l’état média
À la reprise, vérifiez dans cet ordre :
- Le document existe-t-il encore ? Une restauration depuis le cache arrière/avant peut réutiliser un document ; une page supprimée peut être recréée.
- Le lecteur est-il toujours propriétaire de la vidéo ? Un composant démonté peut avoir détruit hls.js, détaché MediaSource ou supprimé les écouteurs.
- Le navigateur a-t-il encore des données actuelles ? Les playlists live et les jetons peuvent avoir expiré pendant l’absence.
- La position est-elle recherchable ? Comparez
currentTimeaux plagesseekableactuelles au lieu de supposer l’ancienne fenêtre. - La lecture a-t-elle réellement repris ? Examinez la promesse
play(),paused,readyState, les erreurs et la progression du temps.
Les navigateurs peuvent suspendre les minuteurs en arrière-plan. Ne vous fiez donc pas à une minuterie JavaScript pour faire évoluer l’horloge média ou rafraîchir régulièrement l’état. À la reprise, récupérez les données nécessaires plutôt que de simuler les événements manqués.
Récupérez à partir des plages média actuelles
Pour la VOD, conservez la position si elle appartient encore à une plage recherchable. Pour le live, une ancienne position absolue currentTime peut être sortie de la fenêtre DVR glissante. Récupérez la playlist actuelle, calculez la fenêtre recherchable exposée par le lecteur, puis choisissez une position prise en charge — par exemple la position live-sync du lecteur — selon l’intention produit.
Ne sautez pas automatiquement au direct si l’utilisateur regardait volontairement une partie antérieure du DVR. Distinguez position de lecture enregistrée, position désirée et position actuelle disponible. Si le direct a avancé au-delà de l’ancienne position, expliquez ce qui s’est passé et appliquez une règle cohérente.
Lors d’un retour du réseau, séparez les erreurs d’autorisation des échecs de transport. Une réponse 401 ou 403 nécessite généralement une actualisation de l’autorisation ou une action utilisateur, pas des tentatives répétées de fragments. Évitez les boucles de rechargement qui martèlent une source ou effacent les éléments utiles au diagnostic.
Définissez une stratégie de reprise explicite
Enregistrez l’intention de lecture à l’événement où elle est fiable, puis restaurez-la seulement après avoir interrogé l’état média courant. Une stratégie peut être :
- si la vidéo était volontairement en pause, rester en pause ;
- si la source a changé ou le lecteur a été détruit, recréer le lecteur une seule fois ;
- pour la VOD, restaurer la position seulement si elle est toujours recherchable ;
- pour le live, choisir la position parmi la fenêtre actuelle et la politique de reprise ;
- si le navigateur exige un geste utilisateur, présenter un bouton de reprise plutôt que de répéter
play()en boucle ; - si l’autorisation a expiré, la renouveler par le parcours normal de l’application avant de relancer la lecture.
Ajoutez une temporisation avec retour utilisateur, un nombre maximal de tentatives et une journalisation des causes. Le message doit distinguer « appuyez pour reprendre », « reconnexion » et « retour au direct » au lieu de les réunir sous un unique bouton générique.
Testez une matrice contrôlée
Utilisez un flux autorisé et répétez les transitions :
| Dimension | Cas minimaux |
|---|---|
| Transition | Changement d’onglet, changement d’application, verrouillage, déverrouillage, appel interrompant, récupération de processus navigateur |
| Durée | Retour rapide, plus d’un cycle de cible live, après la fenêtre DVR, après expiration d’URL |
| Média | VOD, direct glissant, EVENT/DVR, vidéo avec audio et, si pris en charge, audio seul |
| Réseau | Réseau inchangé, hors ligne en arrière-plan, Wi-Fi vers mobile, portail captif nécessitant une nouvelle connexion |
| Résultat attendu | Continuer, pause sûre, restaurer une position, rejoindre le point de synchronisation live ou demander une action utilisateur |
Pour chaque essai, notez le modèle d’appareil et la version système, le navigateur/WebView et le lecteur, la transition et sa durée, les événements réellement observés, l’intention enregistrée, les numéros de séquence et plages recherchables avant/après, la première requête au retour, le résultat de la promesse play() et le temps nécessaire pour atteindre playing. Les plateformes sans test physique doivent être indiquées « documentation consultée » ou « non testées ».
Un onglet de bureau bridé ne valide pas le verrouillage d’un iPhone, les règles audio de Safari iOS ou la politique d’une WebView Android. Ne présentez pas une émulation comme un essai sur appareil.
Rédigez le rapport autour d’un seul retour
Un rapport exploitable peut préciser : « À la position média 42, le téléphone de test a été verrouillé alors que la fenêtre live allait de 40 à 70. Au retour, elle allait de 58 à 88 : la position 42 était expirée. La nouvelle playlist a été récupérée ; le lecteur a indiqué le point live-sync 76. Après un geste utilisateur autorisé, la lecture a démarré à 76 et l’événement playing a confirmé la reprise. » Cette formulation identifie l’expiration de la chronologie au lieu d’accuser vaguement le mobile.
Incluez le comportement d’arrière-plan attendu, l’intention mémorisée, la transition exacte, les événements du cycle de vie reçus, la chronologie avant/après, des éléments réseau expurgés, les changements de propriété du lecteur, le détail des rejets de promesse et l’identité de l’appareil physique. Sans preuve, n’affirmez pas que le système a tué, gelé ou supprimé la page.
Références principales
- MDN : Page Visibility API
- MDN : événement
visibilitychange - Chrome for Developers : Page Lifecycle API
- MDN :
HTMLMediaElement.play() - MDN :
HTMLMediaElement.currentTime - RFC 8216 : HTTP Live Streaming
- hls.js API :
liveSyncPosition, latence et méthodes de cycle de vie
Partez de la transition réellement effectuée et suivez le premier état ou la première requête qui change. Enregistrez l’intention avant la disparition de la page, reconstruisez la fenêtre actuelle au retour, puis reprenez à une position prise en charge au lieu de restaurer un état périmé. Cette méthode rend la reprise au premier plan testable, même si la plateforme ne garantit pas la lecture en arrière-plan.