Files
Ferme/frontend/composables/useBarcodeScanner.ts
tristan 7f78454553
All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
[#FER-13] Faire des recherches sur le scanner des bêtes (!42)
| Numéro du ticket | Titre du ticket |
|------------------|-----------------|
|                  |                 |

## Description de la PR

## Modification du .env

## Check list

- [x] Pas de régression
- [x] TU/TI/TF rédigée
- [x] TU/TI/TF OK
- [x] CHANGELOG modifié

Reviewed-on: #42
Co-authored-by: tristan <tristan@yuno.malio.fr>
Co-committed-by: tristan <tristan@yuno.malio.fr>
2026-03-26 16:51:27 +00:00

114 lines
3.3 KiB
TypeScript

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<string | null>(null)
let detector: InstanceType<Window['BarcodeDetector']> | 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
}
}