import type { BarcodeFormat, DetectedBarcode } from "barcode-detector/pure"; /*** select camera ***/ export interface Camera { label: string; constraints: { deviceId?: string; facingMode: string; }; } export const defaultConstraintOptions: Array = [ { label: "rear camera", constraints: { facingMode: "environment" } }, { label: "front camera", constraints: { facingMode: "user" } }, ]; export async function getAvailableCameras(): Promise> { // NOTE: on iOS we can't invoke `enumerateDevices` before the user has given // camera access permission. `QrcodeStream` internally takes care of // requesting the permissions. The `camera-on` event should guarantee that this // has happened. const devices = await navigator.mediaDevices.enumerateDevices(); const videoDevices = devices.filter(({ kind }) => kind === "videoinput"); return [ ...defaultConstraintOptions, ...videoDevices.map(({ deviceId, label }) => ({ label: `${label} (ID: ${deviceId})`, constraints: { deviceId, facingMode: "custom" }, })), ]; } /*** track functons ***/ export function paintOutline(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) { for (const detectedCode of detectedCodes) { const [firstPoint, ...otherPoints] = detectedCode.cornerPoints; ctx.strokeStyle = "red"; ctx.beginPath(); ctx.moveTo(firstPoint.x, firstPoint.y); for (const { x, y } of otherPoints) { ctx.lineTo(x, y); } ctx.lineTo(firstPoint.x, firstPoint.y); ctx.closePath(); ctx.stroke(); } } export function paintBoundingBox(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) { for (const detectedCode of detectedCodes) { const { boundingBox: { x, y, width, height }, } = detectedCode; ctx.lineWidth = 2; ctx.strokeStyle = "#007bff"; ctx.strokeRect(x, y, width, height); } } export function paintCenterText(detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) { for (const detectedCode of detectedCodes) { const { boundingBox, rawValue } = detectedCode; const centerX = boundingBox.x + boundingBox.width / 2; const centerY = boundingBox.y + boundingBox.height / 2; const fontSize = Math.max(12, (50 * boundingBox.width) / ctx.canvas.width); ctx.font = `bold ${fontSize}px sans-serif`; ctx.textAlign = "center"; ctx.lineWidth = 3; ctx.strokeStyle = "#35495e"; ctx.strokeText(detectedCode.rawValue, centerX, centerY); ctx.fillStyle = "#5cb984"; ctx.fillText(rawValue, centerX, centerY); } } export const trackFunctionOptions = [ { text: "nothing (default)", value: undefined }, { text: "outline", value: paintOutline }, { text: "centered text", value: paintCenterText }, { text: "bounding box", value: paintBoundingBox }, { text: "mixed", value: (detectedCodes: DetectedBarcode[], ctx: CanvasRenderingContext2D) => { paintOutline(detectedCodes, ctx); paintCenterText(detectedCodes, ctx); }, }, ]; /*** barcode formats ***/ export const barcodeFormats: Array = [ "aztec", "code_128", "code_39", "code_93", "codabar", "databar", "databar_expanded", "data_matrix", "dx_film_edge", "ean_13", "ean_8", "itf", "maxi_code", "micro_qr_code", "pdf417", "qr_code", "rm_qr_code", "upc_a", "upc_e", "linear_codes", "matrix_codes", ]; /*** error handling ***/ export function handleScannerError(err: Error) { let error = `[${err.name}]: `; if (err.name === "NotAllowedError") { error += "you need to grant camera access permission"; } else if (err.name === "NotFoundError") { error += "no camera on this device"; } else if (err.name === "NotSupportedError") { error += "secure context required (HTTPS, localhost)"; } else if (err.name === "NotReadableError") { error += "is the camera already in use?"; } else if (err.name === "OverconstrainedError") { error += "installed cameras are not suitable"; } else if (err.name === "StreamApiNotSupportedError") { error += "Stream API is not supported in this browser"; } else if (err.name === "InsecureContextError") { error += "Camera access is only permitted in secure context. Use HTTPS or localhost rather than HTTP."; } else { error += err.message; } return error; }