// 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'; } // Renvoie un code de statut plutôt que de laisser remonter l'exception : une DOMException // traversée par le pont JS→C# perd son `name` (mesuré : C# ne reçoit que « Permission denied // undefined »), or c'est précisément ce nom qui dit à l'utilisateur ce qui lui manque. export async function demarrer(element) { video = element; try { // 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, }); } catch (e) { switch (e.name) { case 'NotAllowedError': case 'SecurityError': return 'permission-refusee'; case 'NotFoundError': case 'DevicesNotFoundError': case 'OverconstrainedError': return 'aucune-camera'; case 'NotReadableError': case 'TrackStartError': return 'camera-occupee'; default: return 'erreur:' + (e.name || 'Error') + ' — ' + (e.message || ''); } } video.srcObject = flux; video.setAttribute('playsinline', ''); await video.play(); canvas = document.createElement('canvas'); ctx = canvas.getContext('2d', { willReadFrequently: true }); return 'ok'; } // 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; }