MIF_E31221407_FE/composables/barcodeScanner.ts

150 lines
4.8 KiB
TypeScript

import { ref, onMounted, onBeforeUnmount, type Ref } from 'vue'
import { BrowserMultiFormatReader, type IScannerControls } from '@zxing/browser'
export function useBarcodeScanner(videoElement: Ref<HTMLVideoElement | null>) {
const barcode = ref<string | null>(null)
const isSupported = ref<boolean>(false)
const error = ref<string | null>(null)
const status = ref<'idle' | 'changing' | 'error' | 'scanning' | 'scanned'>('idle')
const permissionStatus = ref<'loading' | 'allowed' | 'denied'>('loading')
let codeReader: BrowserMultiFormatReader
let controls: IScannerControls
let devices: MediaDeviceInfo[] = []
const currentDeviceIndex = ref(0)
const checkCameraSupport = (): boolean => {
return !!(navigator.mediaDevices && navigator.mediaDevices.getUserMedia)
}
const requestCameraPermission = async (): Promise<boolean> => {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true })
stream.getTracks().forEach(track => track.stop())
permissionStatus.value = 'allowed'
return true
} catch (err) {
error.value = 'Izin kamera ditolak atau gagal.'
permissionStatus.value = 'denied'
console.error('[BarcodeScanner] Permission error:', err)
return false
}
}
const startScanner = async (deviceId?: string) => {
if (!checkCameraSupport()) {
error.value = 'Perangkat tidak mendukung akses kamera.'
status.value = 'error'
console.warn('[BarcodeScanner] getUserMedia tidak didukung.')
return
}
if (permissionStatus.value !== 'allowed') {
const granted = await requestCameraPermission()
if (!granted) return
}
codeReader = new BrowserMultiFormatReader()
try {
devices = await BrowserMultiFormatReader.listVideoInputDevices()
if (!devices.length) throw new Error('Kamera tidak ditemukan')
// Otomatis pakai kamera belakang kalau ada
if (!deviceId) {
const backCam = devices.find(device =>
/back|rear/i.test(device.label)
)
if (backCam) {
currentDeviceIndex.value = devices.findIndex(d => d.deviceId === backCam.deviceId)
deviceId = backCam.deviceId
} else {
// Fallback ke kamera pertama
currentDeviceIndex.value = 0
deviceId = devices[0].deviceId
}
}
controls = await codeReader.decodeFromVideoDevice(
deviceId,
videoElement.value!,
(result, err) => {
status.value = 'scanning'
if (result) {
barcode.value = result.getText()
status.value = 'scanned'
}
}
)
} catch (err) {
console.error('[BarcodeScanner] Gagal inisialisasi:', err)
error.value = (err as Error).message
status.value = 'error'
throw new Error('[BarcodeScanner] Gagal inisialisasi')
}
}
const stopScanner = () => {
if (codeReader && controls) {
controls.stop()
}
}
const switchCamera = async () => {
try {
status.value = 'changing'
if (devices.length <= 1) {
console.warn('[BarcodeScanner] Hanya ada satu kamera, tidak bisa switch.')
status.value = 'idle'
return
}
stopScanner()
currentDeviceIndex.value = (currentDeviceIndex.value + 1) % devices.length
await startScanner(devices[currentDeviceIndex.value].deviceId)
status.value = 'idle'
} catch (error) {
console.error('[BarcodeScanner] Gagal switch kamera:', error)
status.value = 'error'
}
}
onMounted(async () => {
isSupported.value = checkCameraSupport()
if (!isSupported.value) {
console.warn('[BarcodeScanner] Kamera tidak didukung di browser ini.')
return
}
await requestCameraPermission()
if (permissionStatus.value !== 'allowed') return
devices = await BrowserMultiFormatReader.listVideoInputDevices()
if (devices.length === 0) {
error.value = 'Tidak ada kamera yang tersedia.'
return
}
await startScanner()
})
onBeforeUnmount(() => {
stopScanner()
})
return {
barcode,
error,
isSupported,
startScanner,
stopScanner,
switchCamera,
devices,
status,
permissionStatus
}
}