Corriger deux blocages du scan trouves en navigateur, et consigner les mesures reelles

Les deux defauts ont ete constates en executant reellement l'application, pas par
relecture :

1. L'import du module JS partait en 404 en developpement. L'import map generee par
   Blazor reecrit « ./js/scanner-camera.js » vers un nom empreinte que l'API hote ne
   sert pas (UseStaticFiles ignore les points d'entree empreintes). Une chaine de
   requete sur le specifieur empeche la reecriture ; en publication rien n'est
   reecrit pour ce fichier, le comportement est donc identique dans les deux modes.

2. Les messages d'erreur camera etaient tous generiques. Une DOMException perd son
   « name » en traversant le pont JS vers C# : un refus de permission arrivait sous
   la forme « Permission denied undefined ». Le JS attrape donc l'erreur et renvoie
   un code de statut stable, que le C# traduit en message utile.

CLAUDE.md est mis a jour avec les mesures reelles obtenues en WASM, qui manquaient :
la performance de decodage (l'estimation de 5-10 ms/frame est confirmee, l'AOT reste
inutile) et le surcout de payload, nettement superieur aux 192 Ko documentes une fois
comptees les assemblies BCL que ZXing empeche le trimmer de retirer.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
mathieu
2026-08-18 02:10:58 +02:00
co-authored by Claude Opus 5
parent f7ad9582f6
commit 3a00591117
3 changed files with 95 additions and 34 deletions
@@ -67,6 +67,14 @@
private enum Etat { Demarrage, Actif, Erreur }
// Le « ?m=1 » n'est pas cosmétique. En développement, l'import map généré par Blazor
// fait pointer « ./js/scanner-camera.js » vers un nom empreinté que l'API hôte ne sert
// pas (elle utilise UseStaticFiles, qui ignore les points d'entrée empreintés) : l'import
// échoue en 404 et le scan ne démarre jamais. Une chaîne de requête empêche l'import map
// de réécrire le chemin ; en publication, aucune réécriture n'est générée pour ce fichier,
// donc le comportement est identique. Vérifié dans les deux modes.
private const string CheminModule = "./js/scanner-camera.js?m=1";
// ~12 images/s : bien assez pour scanner, et deux fois moins de travail que 25 fps.
private const int IntervalleMs = 80;
@@ -96,7 +104,7 @@
try
{
_module = await JS.InvokeAsync<IJSObjectReference>("import", "./js/scanner-camera.js");
_module = await JS.InvokeAsync<IJSObjectReference>("import", CheminModule);
var diagnostic = await _module.InvokeAsync<string>("diagnostic");
if (diagnostic != "ok")
@@ -114,29 +122,32 @@
return;
}
await _module.InvokeVoidAsync("demarrer", _video);
// On ne laisse jamais un écran noir sans explication : chaque code dit précisément
// ce qui manque à l'utilisateur pour que le scan fonctionne.
var statut = await _module.InvokeAsync<string>("demarrer", _video);
if (statut != "ok")
{
Echouer(statut switch
{
"permission-refusee" =>
"L'accès à la caméra a été refusé. Autorisez-le dans les réglages du navigateur, "
+ "ou saisissez l'ISBN à la main.",
"aucune-camera" =>
"Aucune caméra utilisable n'a été trouvée sur cet appareil. Saisissez l'ISBN à la main.",
"camera-occupee" =>
"La caméra est déjà utilisée par une autre application. Fermez-la, ou saisissez l'ISBN à la main.",
_ => $"La caméra n'a pas pu démarrer ({statut[(statut.IndexOf(':') + 1)..].Trim()}). "
+ "Saisissez l'ISBN à la main.",
});
return;
}
_etat = Etat.Actif;
StateHasChanged();
_boucle = new CancellationTokenSource();
_ = BoucleAsync(_boucle.Token);
}
catch (JSException ex)
{
// On ne laisse jamais un écran noir sans explication : le nom de l'erreur DOM
// dit précisément ce qui manque à l'utilisateur pour que ça marche.
Echouer(NomErreurDom(ex.Message) switch
{
"NotAllowedError" or "SecurityError" =>
"L'accès à la caméra a été refusé. Autorisez-le dans les réglages du navigateur, "
+ "ou saisissez l'ISBN à la main.",
"NotFoundError" or "DevicesNotFoundError" or "OverconstrainedError" =>
"Aucune caméra utilisable n'a été trouvée sur cet appareil. Saisissez l'ISBN à la main.",
"NotReadableError" or "TrackStartError" =>
"La caméra est déjà utilisée par une autre application. Fermez-la, ou saisissez l'ISBN à la main.",
_ => $"La caméra n'a pas pu démarrer ({ex.Message}). Saisissez l'ISBN à la main.",
});
}
catch (Exception ex)
{
Echouer($"La caméra n'a pas pu démarrer ({ex.Message}). Saisissez l'ISBN à la main.");
@@ -216,13 +227,6 @@
StateHasChanged();
}
private static string NomErreurDom(string message)
{
// Blazor sérialise l'erreur DOM sous la forme "NotAllowedError: Permission denied".
var separateur = message.IndexOf(':');
return separateur > 0 ? message[..separateur].Trim() : message.Trim();
}
private async Task ArreterCameraAsync()
{
_boucle?.Cancel();