Ajouter le scan camera du code-barres ISBN
Le decodage EAN-13/EAN-8 est fait en C# par ZXing.Net (IsbnScanner), conformement a la decision actee dans CLAUDE.md : le JavaScript ne fournit que les pixels de la camera, aucune logique de decodage ne lui est confiee. Le scan se greffe en amont du flux d'ajout par ISBN existant : des qu'un code est lu et reconnu comme ISBN-13 valide, la recherche de notices est declenchee sans que l'utilisateur ait a retaper quoi que ce soit. La saisie manuelle reste accessible en permanence, y compris quand la camera echoue. Points traites explicitement : - camera arriere par defaut (facingMode "environment", en "ideal" pour ne pas echouer sur un PC sans camera arriere) ; - liberation de la camera a l'annulation, au succes et au demontage du composant ; - cadence limitee a ~12 images/s et decodage restreint a la bande centrale reduite a 640 px de large : inutile de decoder chaque frame en pleine taille ; - messages distincts pour permission refusee, absence de camera, camera occupee et contexte non securise (getUserMedia exige HTTPS ou localhost : une IP de reseau local en http ne marchera jamais, c'est une source de confusion garantie). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -449,3 +449,36 @@ body {
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Scan du code-barres --- */
|
||||
|
||||
.scanner-cadre {
|
||||
position: relative;
|
||||
background: #000;
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
aspect-ratio: 4 / 3;
|
||||
}
|
||||
|
||||
.scanner-video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Le viseur matérialise la bande réellement décodée : viser ailleurs ne sert à rien. */
|
||||
.scanner-viseur {
|
||||
position: absolute;
|
||||
left: 5%;
|
||||
right: 5%;
|
||||
top: 27.5%;
|
||||
height: 45%;
|
||||
border: 2px solid rgba(255, 255, 255, 0.85);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 0 0 100vmax rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
.scanner-mesure {
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// Rôle unique de ce fichier : ouvrir la caméra et fournir des pixels à C#.
|
||||
// AUCUN décodage ici — il est fait par ZXing.Net côté C# (décision actée dans CLAUDE.md).
|
||||
// getUserMedia et canvas/getImageData n'ont pas d'équivalent C#, d'où ces quelques lignes.
|
||||
|
||||
let flux = null;
|
||||
let video = null;
|
||||
let canvas = null;
|
||||
let ctx = null;
|
||||
let derniersPixels = null;
|
||||
|
||||
// getUserMedia exige un contexte sécurisé : HTTPS, ou localhost.
|
||||
// Une IP de réseau local (192.168.x.x) en http NE l'est PAS — piège classique en dev.
|
||||
export function diagnostic() {
|
||||
if (!window.isSecureContext) return 'contexte-non-securise';
|
||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) return 'api-absente';
|
||||
return 'ok';
|
||||
}
|
||||
|
||||
export async function demarrer(element) {
|
||||
video = element;
|
||||
// facingMode "environment" = caméra arrière : on scanne un livre tenu devant soi.
|
||||
// "ideal" et non "exact" pour ne pas échouer sur un PC qui n'a qu'une webcam frontale.
|
||||
flux = await navigator.mediaDevices.getUserMedia({
|
||||
video: { facingMode: { ideal: 'environment' }, width: { ideal: 1280 }, height: { ideal: 720 } },
|
||||
audio: false,
|
||||
});
|
||||
video.srcObject = flux;
|
||||
video.setAttribute('playsinline', '');
|
||||
await video.play();
|
||||
canvas = document.createElement('canvas');
|
||||
ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
}
|
||||
|
||||
// Capture une frame et la garde en mémoire. Renvoie [largeur, hauteur], ou null si
|
||||
// la vidéo n'est pas encore prête. Les pixels se récupèrent ensuite via pixels().
|
||||
export function capturer(largeurMax, partHauteur) {
|
||||
if (!video || !ctx || video.readyState < 2 || !video.videoWidth) return null;
|
||||
const echelle = Math.min(1, largeurMax / video.videoWidth);
|
||||
const l = Math.round(video.videoWidth * echelle);
|
||||
const h = Math.round(video.videoHeight * echelle);
|
||||
if (canvas.width !== l || canvas.height !== h) { canvas.width = l; canvas.height = h; }
|
||||
ctx.drawImage(video, 0, 0, l, h);
|
||||
// On ne renvoie que la bande centrale, celle que l'utilisateur vise : moins de pixels
|
||||
// à transférer et à décoder, donc moins de travail par frame côté C#.
|
||||
const hb = Math.max(1, Math.round(h * partHauteur));
|
||||
const y = Math.round((h - hb) / 2);
|
||||
derniersPixels = new Uint8Array(ctx.getImageData(0, y, l, hb).data.buffer);
|
||||
return [l, hb];
|
||||
}
|
||||
|
||||
export function pixels() {
|
||||
return derniersPixels;
|
||||
}
|
||||
|
||||
// Libérer la caméra : sinon le voyant reste allumé et la batterie se vide,
|
||||
// ce que l'utilisateur perçoit — à raison — comme un dysfonctionnement.
|
||||
export function arreter() {
|
||||
if (flux) { flux.getTracks().forEach(t => t.stop()); flux = null; }
|
||||
if (video) { video.srcObject = null; video = null; }
|
||||
canvas = null; ctx = null; derniersPixels = null;
|
||||
}
|
||||
Reference in New Issue
Block a user