import { ref, onUnmounted } from 'vue' declare global { interface Window { BarcodeDetector: new (options?: { formats: string[] }) => { detect(source: HTMLVideoElement | ImageBitmapSource): Promise<{ rawValue: string }[]> } } const BarcodeDetector: Window['BarcodeDetector'] | undefined } export function useBarcodeScanner(onDetected: (code: string) => void) { const isSupported = ref('BarcodeDetector' in globalThis) const isScanning = ref(false) const error = ref(null) let detector: InstanceType | null = null let stream: MediaStream | null = null let animationFrameId: number | null = null let lastDetectedCode = '' let lastDetectedTime = 0 const COOLDOWN_MS = 2000 async function start(videoElement: HTMLVideoElement) { if (!isSupported.value) { error.value = 'BarcodeDetector non supporté. Utilisez Chrome sur Android.' return } try { detector = new BarcodeDetector({ formats: ['code_39', 'code_128'] }) stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment', width: { ideal: 1280 }, height: { ideal: 720 } } }) videoElement.srcObject = stream await videoElement.play() isScanning.value = true error.value = null scanLoop(videoElement) } catch (e) { error.value = e instanceof Error ? e.message : 'Erreur lors du démarrage de la caméra' isScanning.value = false } } function scanLoop(videoElement: HTMLVideoElement) { if (!isScanning.value || !detector) return animationFrameId = requestAnimationFrame(async () => { try { if (videoElement.readyState >= HTMLMediaElement.HAVE_ENOUGH_DATA) { const barcodes = await detector!.detect(videoElement) if (barcodes.length > 0) { const code = barcodes[0].rawValue.slice(4) const now = Date.now() if (code !== lastDetectedCode || now - lastDetectedTime > COOLDOWN_MS) { lastDetectedCode = code lastDetectedTime = now if (navigator.vibrate) { navigator.vibrate(100) } onDetected(code) } } } } catch { // Detection error on single frame, continue } scanLoop(videoElement) }) } function stop() { isScanning.value = false if (animationFrameId !== null) { cancelAnimationFrame(animationFrameId) animationFrameId = null } if (stream) { stream.getTracks().forEach(track => track.stop()) stream = null } detector = null } onUnmounted(() => { stop() }) return { isSupported, isScanning, error, start, stop } }