Extend the displayed position between Jellyfin observations without counting a second twice, freeze it as explicitly uncertain when the session observation or the player check-in goes stale, and fetch a fresh state after a network cut, a suspended page or a restored one before presenting the display as synchronized again. Rebuild both tracks and the history when the media or source changed during an interruption, keep a disappeared session selected without switching playbacks, and discard in-flight observations computed before a wake-up. Validate the packaged companion end to end: network cuts, suspension, session stop and media change on the installed server, plus a seek measurement against the burnt-in timecode of the Web witness player (323 ms worst stable gap, 788 ms seek recovery). Record versions, conditions and limits in docs/validation/issue-6.md; only Jellyfin Web is declared validated.
8.0 KiB
Validation du lot #6 — reprise du suivi et validation réelle
Protocole
Paquet 0.6.0, Jellyfin Server 12.0.0 (image et digest figés dans
tests/run.sh), Jellyfin Web 12.0.0 livré avec cette image, Chromium
149.0.7827.55, Playwright 1.61.1. Compagnon de 375 × 812 pixels,
compte ordinaire Alice, hébergement HTTP local sans préfixe puis sous
/jellyfin. Les autres lecteurs, HTTPS et les reverse proxies ne sont pas
qualifiés par ce lot.
bash tests/run.sh # suite complète
bash tests/run.sh --test-name-pattern='external subtitles' # mesures
bash tests/run.sh --test-name-pattern='tracking recovery' # reprise
node --test tests/recovery.test.mjs # frontière navigateur/API
Le média Compagnon-test est généré par FFmpeg avec un timecode PTS incrusté à
25 images/s et deux pistes externes anglaises et françaises ; les copies
Embedded-* portent les pistes embarquées SRT, WebVTT et ASS du lot #5.
Modèle de suivi
Le serveur avance PositionTicks par pas d'une seconde à partir de la dernière
remontée du lecteur ; la remontée elle-même n'arrive qu'à la cadence du client
(dix secondes pour Jellyfin Web). Le compagnon reconstruit la position rapportée
lors de la remontée en soustrayant les secondes entières déjà comptées, puis
ajoute une seule fois le temps écoulé : aucune seconde n'est comptée deux fois et
le pas d'une seconde ne fait pas reculer l'affichage. La plus haute position
reconstruite de la fenêtre récente est conservée pour absorber une seconde
retardée par la charge du serveur. L'estimation locale :
- ne reprend qu'après une observation fraîche de la session choisie (moins de 2,5 s) et une remontée récente du lecteur (moins de 30 s, sauf en pause) ;
- se fige sinon, avec le statut explicite « Suivi incertain » ; une seconde manquante est remplacée par l'horloge locale pendant au plus 2,5 s ;
- est invalidée à chaque retour de veille, de page ou de réseau, qui déclenche une observation immédiate et annule toute requête en vol ; l'affichage n'est présenté de nouveau comme synchronisé qu'après la réponse ;
- est abandonnée avec l'historique et les pistes lors d'une coupure constatée (« Suivi indisponible ») ; au retour, le contexte est reconstruit.
Une session disparue est signalée sans basculer sur une autre lecture, un changement de média ou de source pendant une coupure reconstruit les deux pistes et l'historique, et un jeton refusé suit le parcours du lot #7 (retour à Quick Connect, données du compte précédent effacées, jeton non réutilisé).
Vérification de reprise
Le parcours installé tracking recovery resynchronizes both languages after a network cut, a suspension and a session stop exécute, avec le lecteur Web réel
et les pistes externes anglaises et françaises :
| Situation | Vérification |
|---|---|
| Coupure réseau | « Suivi indisponible », historique et pistes effacés, connexion conservée ; au retour, les deux langues sont de nouveau consultables |
| Navigateur suspendu | Les observations sont retenues ; l'historique se fige, ne recule pas et n'avance plus ; au réveil, « Reprise après suspension », observation fraîche puis « Suivi actif » |
| Arrêt de session pendant une coupure | Une autre lecture existe, mais la session suivie reste sélectionnée et signalée indisponible ; aucun basculement |
| Changement de média pendant une coupure | Passage d'une piste embarquée WebVTT à une piste embarquée SRT : le média, la source, les pistes et l'historique sont reconstruits, sans texte de l'ancien contexte |
tests/recovery.test.mjs rejoue ces états et la seconde retardée à la frontière
navigateur/API, y compris le gel d'une estimation périmée et l'absence de saut
en avant au réveil. Le refus d'authentification pendant le suivi reste couvert
par les tests des lots #7 et #8 ; les droits ne sont
ni étendus ni transférés entre comptes.
Précision contre le lecteur principal
La mesure est indépendante de l'API des sessions : le test capture des images
réellement décodées par le lecteur Web (requestVideoFrameCallback().mediaTime),
relève la position affichée par le compagnon au même instant et conserve les PNG
permettant de relire le timecode incrusté. Les fichiers bruts sont dans
issue-6-measurements.json et une image témoin à
62 s dans issue-6-frame.png.
Mesure finale du 18 septembre 2026, plugin 0.6.0 :
| Timecode de l'image (s) | Position compagnon (s) | Écart absolu (ms) |
|---|---|---|
| 62,000 | 61,737 | 263 |
| 62,680 | 62,437 | 243 |
| 63,360 | 63,037 | 323 |
| 64,040 | 63,737 | 303 |
| 64,720 | 64,437 | 283 |
| 65,400 | 65,137 | 263 |
| 66,080 | 65,837 | 243 |
| 66,760 | 66,437 | 323 |
Lecture stable : réussite du seuil strict de 500 ms, avec un écart maximal de 323 ms. Déplacement : réussite, recalage en 788 ms avant l'affichage de la fenêtre attendue, la position affichée étant alors à moins de deux secondes de la cible. L'écart résiduel vient surtout de la latence entre la position échantillonnée par le lecteur et sa remontée au serveur, de la cadence de remontée (dix secondes pour Jellyfin Web) et de la latence d'affichage ; un décalage d'horloge entre le navigateur compagnon et le serveur s'y ajoute s'il existe. Il n'est pas masqué.
Clients
| Client | Version | Résultat |
|---|---|---|
| Jellyfin Web (lecteur témoin) | 12.0.0, Chromium 149.0.7827.55 | validé : seuils de 500 ms et de deux secondes respectés |
| Applications mobiles, TV et autres lecteurs Jellyfin | — | non testés : aucun résultat n'est revendiqué |
Une réussite sur le lecteur Web ne vaut pas validation d'un autre client. Si un lecteur ne remonte pas sa position à une cadence suffisante, l'écart sera plus grand ; aucune précision supérieure aux informations reçues n'est promise.
Limites
Ce lot n'ajoute ni contrôle de lecture, ni OCR, ni traduction automatique, ni prise en charge garantie des vitesses autres que 1×. Les délais d'extraction à froid et à chaud restent ceux du lot #5 et ne sont pas des mesures de synchronisation. L'endpoint natif de sous-titres non authentifié signalé aux lots #3 et #5 reste inchangé. En lecture, une remontée vieille de plus de trente secondes fait passer l'affichage en suivi incertain ; en pause, l'absence de remontée ne le fait pas basculer. Aucune limite de ce lot n'exige de modifier un lecteur, d'ajouter un service ou de réduire les objectifs approuvés ; la précision est conditionnée à une horloge du navigateur compagnon alignée sur celle du serveur.
La suspension du navigateur est vérifiée à la frontière de ses événements de cycle de vie (visibilité, reprise, restauration de page) avec des observations retenues : Chromium sans interface ne gèle pas réellement une page. Le comportement évalué — affichage figé, observation fraîche exigée avant la reprise — est celui du navigateur réel, mais le gel du minuteur lui-même n'est pas reproduit.
Revue
Deux agents ont examiné séparément les axes standards et spec sur le diff
complet. Leur constat principal — un changement de média ou de source pouvant
réutiliser brièvement l'ancrage de position du passage précédent — a été corrigé
et revérifié par un test dédié (changement de source pendant une coupure, avec
recul de position). Ont aussi été corrigés : la version du plugin lue depuis le
fichier projet au lieu d'être recopiée dans le rapport, les noms d'horloges et
les unités de ticks clarifiés dans app.js, et la duplication des mocks de test
regroupée dans tests/helpers.mjs et le fichier de reprise. Les points laissés
en jugement sont documentés : borne de remontée à trente secondes pour rester
tolérant avec les clients non mesurés, et suspension simulée par événements.
Aucun constat bloquant restant.