mirror of
https://github.com/JMP-MS/ASPIC.git
synced 2026-10-27 16:07:10 +00:00
V1.011 Tests de dictées vocales
This commit is contained in:
+332
-13
@@ -32,6 +32,7 @@
|
||||
* correspond déjà à "ce qui a été dit avant une pause" ; on ne choisit
|
||||
* pas la durée exacte de cette pause, elle est interne au navigateur —
|
||||
* même mécanisme que celui déjà éprouvé en production dans micro.js).
|
||||
* **Exception : le "mode adresse", voir plus bas.**
|
||||
* - Restitution au fur et à mesure dans une fenêtre dédiée (fond noir,
|
||||
* texte blanc, défilement) : la ligne en cours s'affiche pendant la
|
||||
* dictée (interimResults activé, contrairement à micro.js) puis se fige
|
||||
@@ -55,6 +56,88 @@
|
||||
* dépendre de micro.js lui-même : ce fichier est volontairement autonome et
|
||||
* plus simple, pour ne jamais risquer de contaminer le vrai pilotage avec
|
||||
* de la logique de test jetable.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
* AJOUT DU 3 SEPTEMBRE 2026 (soirée) — "Mode adresse" (email / URL).
|
||||
* ---------------------------------------------------------------------
|
||||
* Origine : essais réels de Jean-Marc montrant que la dictée d'une adresse
|
||||
* mail échoue très souvent (voir réflexions stratégiques). Deux causes
|
||||
* distinctes identifiées à partir d'un export réel de ASP_DicteesTest :
|
||||
*
|
||||
* 1. Le mot "point" (et "tiret", "arobase", "tiret du bas"...) n'est
|
||||
* converti en symbole ("." "-" "@" "_") que de façon incohérente par le
|
||||
* moteur gratuit du navigateur — parfois converti, parfois laissé en
|
||||
* toutes lettres, parfois avalé. "tiret" est en plus régulièrement
|
||||
* confondu avec le verbe "tirer" (homophone). Un problème de
|
||||
* *vocabulaire*, rattrapable par un dictionnaire de conversion.
|
||||
* 2. Le moteur découpe une même adresse dictée en plusieurs lignes
|
||||
* indépendantes (plusieurs événements isFinal) sans que la durée du
|
||||
* silence permette de distinguer fiablement "il continue la même
|
||||
* adresse" de "il change de sujet" (des écarts de 3-4 secondes
|
||||
* s'observent dans les deux cas sur le corpus réel). Un problème de
|
||||
* *segmentation*, qu'un dictionnaire seul ne peut pas résoudre.
|
||||
*
|
||||
* Le "mode adresse" répond aux deux à la fois en réutilisant le principe
|
||||
* déjà éprouvé du mode notation de micro.js (ouverture/clôture explicite)
|
||||
* mais ici au clic plutôt qu'à la phrase, pour ne pas dépendre d'un mot
|
||||
* de clôture qui pourrait lui-même être mal reconnu :
|
||||
* - Activé via le bouton "Mode adresse" de la fenêtre de test.
|
||||
* - Tant qu'il est actif, chaque fragment reconnu (isFinal) est empilé
|
||||
* dans un buffer au lieu d'être enregistré comme une ligne indépendante
|
||||
* — la segmentation du moteur n'a donc plus d'importance.
|
||||
* - Le bouton "Valider" (ou la désactivation du mode) clôt le buffer :
|
||||
* les fragments sont concaténés, passés à normaliserTexteAdresse() qui
|
||||
* applique le dictionnaire de conversion et retire les espaces
|
||||
* restants (une adresse mail/URL n'en contient jamais), puis le résultat
|
||||
* est comparé à une forme attendue (email ou domaine) pour un simple
|
||||
* retour visuel (vert/orange) — indicatif seulement, n'empêche jamais
|
||||
* l'enregistrement (cohérent avec la règle "aucun essai raté supprimé").
|
||||
* - Le texte brut (tel que dicté, fragments concaténés) ET le texte
|
||||
* normalisé sont envoyés à dictees_test.php et conservés tous les deux
|
||||
* (DCT_Texte / DCT_TexteNormalise) — rien n'écrase la matière brute déjà
|
||||
* actée comme précieuse pour la suite ; DCT_TexteNormalise est une
|
||||
* colonne en plus, NULL pour toutes les lignes hors "mode adresse".
|
||||
* ⚠️ Nécessite l'ajout manuel de cette colonne en base avant déploiement
|
||||
* (voir le message livré avec ce fichier — Claude n'exécute jamais de
|
||||
* modification de schéma lui-même, cf. réflexions stratégiques).
|
||||
* - Un compteur simple (conforme / non conforme) s'affiche dans la fenêtre
|
||||
* pour donner une lecture immédiate du taux d'échec au fil des essais.
|
||||
*
|
||||
* Portée volontairement limitée à ce problème précis : aucune tentative
|
||||
* de deviner where s'arrête une adresse dans un flux libre, aucune
|
||||
* interprétation sémantique — juste un buffer explicite et un dictionnaire
|
||||
* de conversion, dans le même esprit "pas de sur-ingénierie" que le reste
|
||||
* du projet.
|
||||
*
|
||||
* ---------------------------------------------------------------------
|
||||
* CORRECTIF DU 3 SEPTEMBRE 2026 (fin de soirée) — fenêtre effacée à
|
||||
* chaque redémarrage automatique du moteur (bug préexistant, pas lié au
|
||||
* "mode adresse" ci-dessus).
|
||||
* ---------------------------------------------------------------------
|
||||
* `ouvrirFenetre()` (qui vide entièrement le contenu affiché) était
|
||||
* appelée à chaque événement "start" du moteur de reconnaissance — or ce
|
||||
* moteur se coupe et se relance tout seul en silence après une pause
|
||||
* (comportement voulu, voir plus haut), ce qui déclenche un nouvel
|
||||
* événement "start" à chaque fois. Avec des dictées enchaînées, ça ne se
|
||||
* voyait pas ; avec une dictée hésitante (adresse mail, beaucoup de
|
||||
* silences de réflexion), les redémarrages deviennent fréquents et la
|
||||
* fenêtre se vide sans cesse avant d'avoir pu être lue — signalé par
|
||||
* l'indicateur micro de Chrome qui clignote en continu. Corrigé en ne
|
||||
* réinitialisant la fenêtre qu'au tout premier "start" suivant un clic
|
||||
* utilisateur réel (`ouvertureInitiale`), jamais lors d'une relance
|
||||
* automatique. Un bloc de diagnostic temporaire (`DIAGNOSTIC_ACTIF`,
|
||||
* fonction logDiag) a été ajouté en parallèle pour visualiser les cycles
|
||||
* démarrage/arrêt/erreur en direct dans la fenêtre — à retirer une fois
|
||||
* le comportement confirmé stable.
|
||||
*
|
||||
* Ce diagnostic a révélé un second problème, plus grave : dans certaines
|
||||
* conditions le moteur enchaîne des centaines de redémarrages par seconde
|
||||
* (des "aborted" apparaissent parmi les erreurs), sans aucun frein côté
|
||||
* code d'origine (relance immédiate à chaque "end", aucun délai). Ajout
|
||||
* d'un coupe-circuit (`cyclesRapidesConsecutifs` / `MAX_CYCLES_RAPIDES`) :
|
||||
* au-delà de 5 redémarrages consécutifs en moins de 800 ms, l'écoute
|
||||
* s'arrête proprement avec un message, plutôt que de boucler indéfiniment
|
||||
* et de noyer la fenêtre de diagnostic.
|
||||
*/
|
||||
(function(){
|
||||
"use strict";
|
||||
@@ -104,6 +187,47 @@
|
||||
reconnaissance.stop();
|
||||
}
|
||||
|
||||
// -------- Mode adresse : dictionnaire de conversion --------
|
||||
// Volontairement limité aux mots utiles à une adresse mail/URL dictée en
|
||||
// français — jamais appliqué en dehors du "mode adresse" (voir plus bas),
|
||||
// pour ne jamais risquer de transformer un "point" ou un "tiret" dit dans
|
||||
// une phrase normale ailleurs dans l'outil.
|
||||
const REMPLACEMENTS_ADRESSE = [
|
||||
[/\btiret du bas\b/g, "_"],
|
||||
[/\btiret bas\b/g, "_"],
|
||||
[/\bunderscore\b/g, "_"],
|
||||
[/\barobase\b/g, "@"],
|
||||
[/\barobas\b/g, "@"],
|
||||
[/\btrait d'union\b/g, "-"],
|
||||
// "tirer" (verbe) est une confusion acoustique fréquente avec "tiret"
|
||||
// dans ce contexte précis (voir export du 3 septembre) — alias
|
||||
// volontairement restreint au mode adresse, jamais appliqué ailleurs.
|
||||
[/\btiret\b/g, "-"],
|
||||
[/\btirer\b/g, "-"],
|
||||
[/\bpoint\b/g, "."]
|
||||
];
|
||||
|
||||
function normaliserTexteAdresse(brut){
|
||||
let t = " " + brut.toLowerCase().trim() + " ";
|
||||
REMPLACEMENTS_ADRESSE.forEach(function(paire){
|
||||
t = t.replace(paire[0], paire[1]);
|
||||
});
|
||||
// Une adresse mail ou une URL ne contient jamais d'espace : tout espace
|
||||
// restant à ce stade vient de la reconnaissance vocale elle-même
|
||||
// (silences entre syllabes/mots), pas d'une intention de l'utilisateur.
|
||||
t = t.replace(/\s+/g, "");
|
||||
return t;
|
||||
}
|
||||
|
||||
const FORME_EMAIL = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
const FORME_DOMAINE = /^[a-z0-9-]+(\.[a-z0-9-]+)+$/;
|
||||
|
||||
function evaluerConformite(normalise){
|
||||
if(FORME_EMAIL.test(normalise)) return {ok:true, categorie:"email"};
|
||||
if(FORME_DOMAINE.test(normalise)) return {ok:true, categorie:"domaine"};
|
||||
return {ok:false, categorie:null};
|
||||
}
|
||||
|
||||
// -------- Fenêtre de restitution --------
|
||||
// Construite ici plutôt que dans ecran_fond.html, pour que ce fichier
|
||||
// reste entièrement autonome — le retirer (remettre micro.js) ne laisse
|
||||
@@ -111,6 +235,16 @@
|
||||
let fenetre = null;
|
||||
let zoneLignes = null;
|
||||
let ligneEnCours = null;
|
||||
let boutonModeAdresse = null;
|
||||
let boutonValider = null;
|
||||
let zoneBuffer = null;
|
||||
let compteurEl = null;
|
||||
|
||||
// État du mode adresse
|
||||
let modeAdresse = false;
|
||||
let bufferFragments = []; // fragments isFinal accumulés depuis l'activation/le dernier "Valider"
|
||||
let compteurConforme = 0;
|
||||
let compteurNonConforme = 0;
|
||||
|
||||
function construireFenetre(){
|
||||
if(fenetre) return;
|
||||
@@ -118,7 +252,7 @@
|
||||
const style = document.createElement("style");
|
||||
style.textContent =
|
||||
"#fenetre-dictees-test{position:fixed;top:8%;left:50%;" +
|
||||
"transform:translateX(-50%);width:min(560px,80vw);height:min(420px,70vh);" +
|
||||
"transform:translateX(-50%);width:min(560px,80vw);height:min(460px,72vh);" +
|
||||
"background:#000;border:1px solid #333;border-radius:6px;" +
|
||||
"box-shadow:0 10px 40px rgba(0,0,0,.6);z-index:950;display:none;" +
|
||||
"flex-direction:column;overflow:hidden;" +
|
||||
@@ -126,10 +260,28 @@
|
||||
"#fenetre-dictees-test.visible{display:flex;}" +
|
||||
"#fenetre-dictees-test .entete{color:#888;font-size:11px;" +
|
||||
"padding:6px 10px;border-bottom:1px solid #222;flex:none;}" +
|
||||
"#fenetre-dictees-test .controles{display:flex;align-items:center;" +
|
||||
"gap:8px;padding:6px 10px;border-bottom:1px solid #222;flex:none;}" +
|
||||
"#fenetre-dictees-test .controles button{font:inherit;font-size:12px;" +
|
||||
"background:#1a1a1a;color:#ccc;border:1px solid #444;border-radius:4px;" +
|
||||
"padding:4px 10px;cursor:pointer;}" +
|
||||
"#fenetre-dictees-test .controles button.actif{background:#2a4d2a;" +
|
||||
"color:#bdf7bd;border-color:#3a6d3a;}" +
|
||||
"#fenetre-dictees-test .controles button:disabled{opacity:.4;" +
|
||||
"cursor:default;}" +
|
||||
"#fenetre-dictees-test .controles .compteur{margin-left:auto;" +
|
||||
"color:#777;font-size:11px;}" +
|
||||
"#fenetre-dictees-test .buffer{padding:6px 10px;color:#ffd76a;" +
|
||||
"font-size:13px;border-bottom:1px solid #222;min-height:1.4em;" +
|
||||
"flex:none;white-space:pre-wrap;}" +
|
||||
"#fenetre-dictees-test .lignes{flex:1;overflow-y:auto;padding:8px 10px;" +
|
||||
"color:#fff;font-size:14px;line-height:1.6;white-space:pre-wrap;}" +
|
||||
"#fenetre-dictees-test .lignes .en-cours{color:#8a8a8a;font-style:italic;}" +
|
||||
"#fenetre-dictees-test .lignes .erreur{color:#e14a5c;}";
|
||||
"#fenetre-dictees-test .lignes .erreur{color:#e14a5c;}" +
|
||||
"#fenetre-dictees-test .lignes .adresse-ok{color:#7be07b;}" +
|
||||
"#fenetre-dictees-test .lignes .adresse-ko{color:#e0a95a;}" +
|
||||
"#fenetre-dictees-test .lignes .adresse-brut{color:#777;font-size:12px;}" +
|
||||
"#fenetre-dictees-test .lignes .diag{color:#6ab8ff;font-size:11px;}";
|
||||
document.head.appendChild(style);
|
||||
|
||||
fenetre = document.createElement("div");
|
||||
@@ -137,15 +289,95 @@
|
||||
fenetre.innerHTML =
|
||||
'<div class="entete">Banque de dictées — Gestion des Contacts ' +
|
||||
'(outil de mise au point, non destiné aux utilisateurs finaux)</div>' +
|
||||
'<div class="controles">' +
|
||||
'<button type="button" class="btn-mode-adresse">Mode adresse : OFF</button>' +
|
||||
'<button type="button" class="btn-valider" disabled>Valider</button>' +
|
||||
'<span class="compteur"></span>' +
|
||||
'</div>' +
|
||||
'<div class="buffer"></div>' +
|
||||
'<div class="lignes"></div>';
|
||||
document.body.appendChild(fenetre);
|
||||
zoneLignes = fenetre.querySelector(".lignes");
|
||||
zoneBuffer = fenetre.querySelector(".buffer");
|
||||
boutonModeAdresse = fenetre.querySelector(".btn-mode-adresse");
|
||||
boutonValider = fenetre.querySelector(".btn-valider");
|
||||
compteurEl = fenetre.querySelector(".compteur");
|
||||
|
||||
boutonModeAdresse.addEventListener("click", function(){
|
||||
if(modeAdresse){
|
||||
// Désactivation : on ne perd jamais un buffer en cours, on le
|
||||
// valide d'abord (même logique que le bouton "Valider").
|
||||
if(bufferFragments.length) validerBufferAdresse();
|
||||
modeAdresse = false;
|
||||
boutonModeAdresse.textContent = "Mode adresse : OFF";
|
||||
boutonModeAdresse.classList.remove("actif");
|
||||
boutonValider.disabled = true;
|
||||
afficherBuffer();
|
||||
} else {
|
||||
modeAdresse = true;
|
||||
boutonModeAdresse.textContent = "Mode adresse : ON";
|
||||
boutonModeAdresse.classList.add("actif");
|
||||
boutonValider.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
boutonValider.addEventListener("click", function(){
|
||||
validerBufferAdresse();
|
||||
});
|
||||
}
|
||||
|
||||
function majCompteur(){
|
||||
if(!compteurEl) return;
|
||||
const total = compteurConforme + compteurNonConforme;
|
||||
compteurEl.textContent = total
|
||||
? "adresses : " + compteurConforme + "/" + total + " conformes"
|
||||
: "";
|
||||
}
|
||||
|
||||
function afficherBuffer(){
|
||||
if(!zoneBuffer) return;
|
||||
zoneBuffer.textContent = modeAdresse
|
||||
? "[mode adresse] " + bufferFragments.join(" ")
|
||||
: "";
|
||||
}
|
||||
|
||||
function validerBufferAdresse(){
|
||||
const brutConcatene = bufferFragments.join(" ").trim();
|
||||
bufferFragments = [];
|
||||
afficherBuffer();
|
||||
if(!brutConcatene) return;
|
||||
|
||||
const normalise = normaliserTexteAdresse(brutConcatene);
|
||||
const conformite = evaluerConformite(normalise);
|
||||
|
||||
if(conformite.ok) compteurConforme++; else compteurNonConforme++;
|
||||
majCompteur();
|
||||
|
||||
const ligneBrut = document.createElement("div");
|
||||
ligneBrut.className = "adresse-brut";
|
||||
ligneBrut.textContent = "brut : " + brutConcatene;
|
||||
zoneLignes.appendChild(ligneBrut);
|
||||
|
||||
const ligneNormalisee = document.createElement("div");
|
||||
ligneNormalisee.className = conformite.ok ? "adresse-ok" : "adresse-ko";
|
||||
ligneNormalisee.textContent = "→ " + normalise +
|
||||
(conformite.ok ? " (" + conformite.categorie + " reconnu)" : " (forme non reconnue)");
|
||||
zoneLignes.appendChild(ligneNormalisee);
|
||||
zoneLignes.scrollTop = zoneLignes.scrollHeight;
|
||||
|
||||
enregistrerDictee(brutConcatene, normalise);
|
||||
reinitialiserMinuteurInactivite();
|
||||
}
|
||||
|
||||
function ouvrirFenetre(){
|
||||
construireFenetre();
|
||||
zoneLignes.innerHTML = "";
|
||||
ligneEnCours = null;
|
||||
bufferFragments = [];
|
||||
compteurConforme = 0;
|
||||
compteurNonConforme = 0;
|
||||
majCompteur();
|
||||
afficherBuffer();
|
||||
fenetre.classList.add("visible");
|
||||
}
|
||||
|
||||
@@ -153,6 +385,7 @@
|
||||
if(fenetre) fenetre.classList.remove("visible");
|
||||
if(zoneLignes) zoneLignes.innerHTML = "";
|
||||
ligneEnCours = null;
|
||||
bufferFragments = [];
|
||||
}
|
||||
|
||||
function afficherEnCours(texte){
|
||||
@@ -187,15 +420,41 @@
|
||||
zoneLignes.scrollTop = zoneLignes.scrollHeight;
|
||||
}
|
||||
|
||||
// -------- DIAGNOSTIC TEMPORAIRE (3 septembre 2026, soir) --------
|
||||
// Ajouté uniquement pour comprendre pourquoi rien ne s'affiche chez
|
||||
// Jean-Marc (indicateur micro de Chrome qui clignote sans arrêt — signe
|
||||
// possible d'un cycle démarrage/arrêt en boucle rapide). Affiche TOUT,
|
||||
// y compris les événements normalement filtrés (ex. "no-speech"), avec
|
||||
// horodatage précis. À RETIRER une fois le problème identifié — ce n'est
|
||||
// pas un affichage destiné à rester.
|
||||
const DIAGNOSTIC_ACTIF = true;
|
||||
let diagCompteurDemarrages = 0;
|
||||
|
||||
function logDiag(texte){
|
||||
if(!DIAGNOSTIC_ACTIF || !zoneLignes) return;
|
||||
const maintenant = new Date();
|
||||
const heure = maintenant.toLocaleTimeString("fr-FR") + "." +
|
||||
String(maintenant.getMilliseconds()).padStart(3, "0");
|
||||
const div = document.createElement("div");
|
||||
div.className = "diag";
|
||||
div.textContent = "[diag " + heure + "] " + texte;
|
||||
zoneLignes.appendChild(div);
|
||||
zoneLignes.scrollTop = zoneLignes.scrollHeight;
|
||||
}
|
||||
|
||||
// -------- Enregistrement --------
|
||||
// Aucune interprétation, aucun filtrage : tout ce qui est capté part vers
|
||||
// dictees_test.php, essai raté compris (décision de Jean-Marc, voir
|
||||
// commentaire d'en-tête).
|
||||
function enregistrerDictee(texte){
|
||||
// commentaire d'en-tête). En mode adresse, le texte normalisé est envoyé
|
||||
// en plus (jamais à la place) du texte brut — voir commentaire d'en-tête
|
||||
// du 3 septembre sur DCT_TexteNormalise.
|
||||
function enregistrerDictee(texte, texteNormalise){
|
||||
const corps = {texte: texte};
|
||||
if(texteNormalise) corps.texteNormalise = texteNormalise;
|
||||
fetch("dictees_test.php", {
|
||||
method: "POST",
|
||||
headers: {"Content-Type": "application/json"},
|
||||
body: JSON.stringify({texte: texte})
|
||||
body: JSON.stringify(corps)
|
||||
}).then(function(reponse){
|
||||
if(!reponse.ok) throw new Error("HTTP " + reponse.status);
|
||||
}).catch(function(){
|
||||
@@ -203,10 +462,23 @@
|
||||
});
|
||||
}
|
||||
|
||||
// Vrai uniquement pour le tout prochain événement "start" déclenché par un
|
||||
// clic utilisateur réel — jamais pour une relance automatique après coupure
|
||||
// silencieuse (voir diagnostic du 3 septembre, soir : la fenêtre entière
|
||||
// était effacée à CHAQUE redémarrage du moteur, pas seulement à l'ouverture
|
||||
// volontaire, ce qui devenait très visible dès que les silences — donc les
|
||||
// redémarrages — se multipliaient pendant une dictée hésitante).
|
||||
let ouvertureInitiale = false;
|
||||
|
||||
function passerEnEcoute(){
|
||||
bouton.classList.add("ecoute");
|
||||
bouton.title = "Le micro est ouvert (banque de dictées contacts — outil de mise au point)";
|
||||
ouvrirFenetre();
|
||||
if(ouvertureInitiale){
|
||||
ouvertureInitiale = false;
|
||||
ouvrirFenetre(); // construit/affiche et réinitialise réellement, une seule fois
|
||||
} else if(fenetre){
|
||||
fenetre.classList.add("visible"); // relance automatique : rien n'est effacé
|
||||
}
|
||||
reinitialiserMinuteurInactivite();
|
||||
}
|
||||
|
||||
@@ -228,18 +500,52 @@
|
||||
}
|
||||
ecouteVoulue = true;
|
||||
erreurBloquante = false;
|
||||
ouvertureInitiale = true;
|
||||
try{
|
||||
reconnaissance.start();
|
||||
}catch(erreur){
|
||||
ecouteVoulue = false;
|
||||
ouvertureInitiale = false;
|
||||
afficherErreur("Impossible de démarrer le micro (" + erreur.name + ").");
|
||||
}
|
||||
});
|
||||
|
||||
reconnaissance.addEventListener("start", passerEnEcoute);
|
||||
// Coupe-circuit anti-boucle (3 septembre 2026, fin de soirée) : le
|
||||
// diagnostic en direct a montré des centaines de redémarrages par
|
||||
// seconde dans certaines conditions (service en défaut, ou toute autre
|
||||
// cause empêchant le moteur de rester ouvert) — sans aucun frein, le code
|
||||
// d'origine relançait immédiatement à chaque "end", ce qui peut boucler
|
||||
// indéfiniment et rendre la fenêtre illisible. Si plusieurs redémarrages
|
||||
// se suivent anormalement vite, on arrête proprement plutôt que de
|
||||
// continuer à boucler en silence.
|
||||
let dernierDemarrageHorodatage = 0;
|
||||
let cyclesRapidesConsecutifs = 0;
|
||||
const SEUIL_CYCLE_RAPIDE_MS = 800;
|
||||
const MAX_CYCLES_RAPIDES = 5;
|
||||
|
||||
reconnaissance.addEventListener("start", function(){
|
||||
diagCompteurDemarrages++;
|
||||
dernierDemarrageHorodatage = Date.now();
|
||||
passerEnEcoute();
|
||||
logDiag("start #" + diagCompteurDemarrages);
|
||||
});
|
||||
|
||||
reconnaissance.addEventListener("end", function(){
|
||||
logDiag("end (ecouteVoulue=" + ecouteVoulue + ", erreurBloquante=" + erreurBloquante + ")");
|
||||
if(ecouteVoulue && !erreurBloquante){
|
||||
const ecoule = Date.now() - dernierDemarrageHorodatage;
|
||||
cyclesRapidesConsecutifs = (ecoule < SEUIL_CYCLE_RAPIDE_MS) ? (cyclesRapidesConsecutifs + 1) : 0;
|
||||
|
||||
if(cyclesRapidesConsecutifs >= MAX_CYCLES_RAPIDES){
|
||||
erreurBloquante = true;
|
||||
revenirAuRepos();
|
||||
afficherErreur("Le micro redémarre en boucle anormalement vite (" +
|
||||
cyclesRapidesConsecutifs + " fois de suite en moins de " +
|
||||
SEUIL_CYCLE_RAPIDE_MS + " ms) — arrêt automatique pour ne pas " +
|
||||
"boucler indéfiniment. Attends quelques secondes puis rouvre le micro.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Le moteur s'est arrêté tout seul (silence prolongé, limite interne
|
||||
// du navigateur...) alors que l'écoute reste voulue : relance
|
||||
// invisible pour l'utilisateur, même principe que micro.js.
|
||||
@@ -260,20 +566,33 @@
|
||||
if(!texte) return;
|
||||
|
||||
if(dernier.isFinal){
|
||||
// Silence détecté par le moteur : la ligne se fige et s'enregistre
|
||||
// immédiatement, essai raté ou non — aucun jugement porté ici sur la
|
||||
// qualité du texte reconnu.
|
||||
figerLigne(texte);
|
||||
enregistrerDictee(texte);
|
||||
if(modeAdresse){
|
||||
// Accumulation dans le buffer plutôt qu'enregistrement immédiat —
|
||||
// voir commentaire d'en-tête du 3 septembre sur le "mode adresse".
|
||||
bufferFragments.push(texte);
|
||||
afficherBuffer();
|
||||
} else {
|
||||
// Silence détecté par le moteur : la ligne se fige et s'enregistre
|
||||
// immédiatement, essai raté ou non — aucun jugement porté ici sur
|
||||
// la qualité du texte reconnu.
|
||||
figerLigne(texte);
|
||||
enregistrerDictee(texte, null);
|
||||
}
|
||||
reinitialiserMinuteurInactivite();
|
||||
} else {
|
||||
// Restitution au fur et à mesure, avant que le silence ne fige la
|
||||
// ligne — demande explicite de Jean-Marc.
|
||||
afficherEnCours(texte);
|
||||
if(modeAdresse){
|
||||
zoneBuffer.textContent = "[mode adresse] " +
|
||||
(bufferFragments.concat([texte])).join(" ");
|
||||
} else {
|
||||
afficherEnCours(texte);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
reconnaissance.addEventListener("error", function(evenement){
|
||||
logDiag("error : " + evenement.error);
|
||||
const messagesBloquants = {
|
||||
"not-allowed": "Micro refusé — vérifie l'autorisation dans le navigateur.",
|
||||
"service-not-allowed": "Micro refusé — vérifie l'autorisation dans le navigateur.",
|
||||
|
||||
Reference in New Issue
Block a user