All checks were successful
Auto Tag Develop / tag (push) Successful in 6s
| 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>
114 lines
3.3 KiB
TypeScript
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
|
|
}
|
|
}
|