import { BarcodeDetector as BarcodeDetectorPonyfill, prepareZXingModule } from "barcode-detector/dist/cjs/ponyfill.js"; import { createUUID } from "../../utils/uuid"; import { getConfig } from "../config"; import scanBeepAudio from "../../../res/scan_beep.ogg"; const scanWeb = { uuid: null, finish: true, stream: null, videoEl: null } const DEFAULT_SCAN_BEEP_AUDIO = scanBeepAudio; const ZXING_READER_WASM_PATH = "lib/reader.wasm"; const WEBSCAN_CLOSE_BUTTON_ID = "__webscan_close_button__"; const WEBSCAN_PICK_IMAGE_BUTTON_ID = "__webscan_pick_image_button__"; const WEBSCAN_CLOSE_ICON_SVG = ""; const WEBSCAN_PICK_IMAGE_ICON_SVG = ""; const WEBSCAN_OVERLAY_BUTTON_SIZE = 40; const WEBSCAN_OVERLAY_BUTTON_EDGE = 8; const WEBSCAN_OVERLAY_BUTTON_GAP = 10; function getDefaultWebScanOverlayButtonStyle() { return "width: " + WEBSCAN_OVERLAY_BUTTON_SIZE + "px; height: " + WEBSCAN_OVERLAY_BUTTON_SIZE + "px; padding: 0; border: 0; border-radius: 50%; background: rgba(0, 0, 0, 0.55); color: #fff; display: flex; align-items: center; justify-content: center; cursor: pointer; z-index: 10000;"; } const currentScriptSrc = typeof document !== "undefined" && document.currentScript && document.currentScript.src; let barcodeDetectorPreparePromise = null; let scanBeepAudioEl = null; let scanBeepAudioSrc = null; let scanBeepUnlockAudioEl = null; let scanBeepUnlocked = false; let scanBeepUnlocking = false; let scanBeepGestureUnlockInstalled = false; /** 极短静音 WAV,仅用于在用户手势内解锁自动播放,不播放真实提示音文件 */ const SILENT_BEEP_UNLOCK_AUDIO = "data:audio/wav;base64,UklGRiQAAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQAAAAA="; function removeEl(id, uuid) { try { let el = document.getElementById(id); if (uuid && el && el.uuid !== uuid) { return; } document.body.removeChild(el); } catch (error) { } } function createEl(tagName, id, style, appendChild) { let el = document.getElementById(id); if (!el) { el = document.createElement(tagName); el.id = id; el.style = style; appendChild && document.body.appendChild(el); } return el; } function stopMediaStream(stream) { try { const tracks = stream && stream.getTracks && stream.getTracks(); if (tracks && tracks.length) { for (let i = 0; i < tracks.length; i++) { tracks[i].stop(); } } } catch (e) { } } function stopActiveWebScan() { scanWeb.uuid = null; stopMediaStream(scanWeb.stream); try { if (scanWeb.videoEl) { scanWeb.videoEl.pause && scanWeb.videoEl.pause(); scanWeb.videoEl.srcObject = null; } } catch (e) { } scanWeb.stream = null; scanWeb.videoEl = null; } function transformPoint(point, width, height, mirrorHorizontal, mirrorVertical, cover) { let x = point.x; let y = point.y; if (cover) { x = cover.x + point.x * cover.scale; y = cover.y + point.y * cover.scale; } return { x: mirrorHorizontal ? width - x : x, y: mirrorVertical ? height - y : y }; } function canvasDrawLine(context, width, height, begin, end, color, mirrorHorizontal, mirrorVertical, cover) { const beginPoint = transformPoint(begin, width, height, mirrorHorizontal, mirrorVertical, cover); const endPoint = transformPoint(end, width, height, mirrorHorizontal, mirrorVertical, cover); context.beginPath(); context.moveTo(beginPoint.x, beginPoint.y); context.lineTo(endPoint.x, endPoint.y); context.lineWidth = 4; context.strokeStyle = color; context.stroke(); } function isMobile() { return typeof navigator !== 'undefined' && /Android|iPhone|iPad|iPod|Mobile/i.test(navigator.userAgent || ""); } function getBarcodeFormats(scanType) { let formats = []; if (!scanType) { scanType = ["qrCode", "barCode"]; } if (scanType.includes('qrCode')) { formats.push('qr_code'); } if (scanType.includes('barCode')) { formats.push( 'ean_13', 'ean_8', 'code_128', 'code_39', 'codabar', 'upc_a', 'upc_e', 'itf', 'aztec', 'data_matrix', 'pdf417' ); } return formats; } function getBarcodeDetectorClass() { if (typeof BarcodeDetector !== 'undefined') { return BarcodeDetector; } return BarcodeDetectorPonyfill; } function getZXingReaderWasmUrl() { if (currentScriptSrc) { return new URL(ZXING_READER_WASM_PATH, currentScriptSrc).href; } return "./" + ZXING_READER_WASM_PATH; } function prepareBarcodeDetector() { const BarcodeDetectorClass = getBarcodeDetectorClass(); if (typeof BarcodeDetector !== 'undefined' || !prepareZXingModule) { return Promise.resolve(BarcodeDetectorClass); } if (!barcodeDetectorPreparePromise) { barcodeDetectorPreparePromise = prepareZXingModule({ fireImmediately: true, overrides: { locateFile: path => { if (path && path.indexOf(".wasm") !== -1) { return getZXingReaderWasmUrl(); } return path; } } }).then(() => BarcodeDetectorClass); } return barcodeDetectorPreparePromise; } function createBarcodeDetector(scanType) { return prepareBarcodeDetector().then(BarcodeDetectorClass => { if (!BarcodeDetectorClass) { throw new Error("BarcodeDetector is not supported"); } const formats = getBarcodeFormats(scanType); if (BarcodeDetectorClass.getSupportedFormats) { return BarcodeDetectorClass.getSupportedFormats().then(supportedFormats => { const supported = formats.filter(format => supportedFormats.indexOf(format) !== -1); if (!supported.length) { throw new Error("No supported barcode formats"); } return new BarcodeDetectorClass({ formats: supported }); }); } return new BarcodeDetectorClass({ formats }); }); } function shouldMirrorWebVideo(stream) { const webScanVideoMirror = getConfig("webScanVideoMirror"); if (typeof webScanVideoMirror === "boolean") { return webScanVideoMirror; } try { const track = stream && stream.getVideoTracks && stream.getVideoTracks()[0]; const settings = track && track.getSettings && track.getSettings(); if (settings && settings.facingMode === "environment") { return false; } if (settings && settings.facingMode === "user") { return true; } } catch (e) { } return !isMobile(); } function shouldMirrorWebVideoVertical() { return getConfig("webScanVideoMirrorVertical") === true; } function getDefaultWebScanCanvasSizePx() { if (typeof window === "undefined") { return { width: 300, height: 300 }; } if (isMobile()) { const side = Math.min(window.innerWidth || 300, window.innerHeight || 300); return { width: Math.max(1, Math.round(side)), height: Math.max(1, Math.round(side)) }; } return { width: 300, height: 300 }; } function getCanvasDisplaySize(canvasEl, fallbackWidth, fallbackHeight) { const rect = canvasEl.getBoundingClientRect(); const width = rect.width || parseFloat(canvasEl.style.width) || fallbackWidth; const height = rect.height || parseFloat(canvasEl.style.height) || fallbackHeight; return { width: Math.max(1, Math.round(width)), height: Math.max(1, Math.round(height)) }; } function getWebScanCloseButtonPositionStyle(canvasEl) { const rect = canvasEl.getBoundingClientRect(); const top = Math.max(0, Math.round(rect.top + WEBSCAN_OVERLAY_BUTTON_EDGE)); const left = Math.max(0, Math.round(rect.right - (WEBSCAN_OVERLAY_BUTTON_SIZE + WEBSCAN_OVERLAY_BUTTON_EDGE))); return "position: fixed; top: " + top + "px; left: " + left + "px;"; } function getWebScanPickImageButtonPositionStyle(canvasEl) { const rect = canvasEl.getBoundingClientRect(); const top = Math.max(0, Math.round(rect.top + WEBSCAN_OVERLAY_BUTTON_EDGE + WEBSCAN_OVERLAY_BUTTON_SIZE + WEBSCAN_OVERLAY_BUTTON_GAP)); const left = Math.max(0, Math.round(rect.right - (WEBSCAN_OVERLAY_BUTTON_SIZE + WEBSCAN_OVERLAY_BUTTON_EDGE))); return "position: fixed; top: " + top + "px; left: " + left + "px;"; } function updateWebScanCloseButtonStyle(closeButtonEl, canvasEl) { if (!closeButtonEl) { return; } const customStyle = getConfig("webScanCloseButtonStyle") || ""; closeButtonEl.style.cssText = getDefaultWebScanOverlayButtonStyle() + getWebScanCloseButtonPositionStyle(canvasEl) + customStyle; } function updateWebScanPickImageButtonStyle(pickButtonEl, canvasEl) { if (!pickButtonEl) { return; } const customStyle = getConfig("webScanPickImageButtonStyle") || ""; pickButtonEl.style.cssText = getDefaultWebScanOverlayButtonStyle() + getWebScanPickImageButtonPositionStyle(canvasEl) + customStyle; } function getCoverDrawOptions(sourceWidth, sourceHeight, targetWidth, targetHeight) { const scale = Math.max(targetWidth / sourceWidth, targetHeight / sourceHeight); const width = sourceWidth * scale; const height = sourceHeight * scale; return { scale, width, height, x: (targetWidth - width) / 2, y: (targetHeight - height) / 2 }; } function drawBarcode(context, width, height, barcode, mirrorHorizontal, mirrorVertical, cover) { const cornerPoints = barcode.cornerPoints; if (cornerPoints && cornerPoints.length) { for (let i = 0; i < cornerPoints.length; i++) { canvasDrawLine(context, width, height, cornerPoints[i], cornerPoints[(i + 1) % cornerPoints.length], "#FF3B58", mirrorHorizontal, mirrorVertical, cover); } return; } if (barcode.boundingBox) { const rect = barcode.boundingBox; const points = [ { x: rect.x, y: rect.y }, { x: rect.x + rect.width, y: rect.y }, { x: rect.x + rect.width, y: rect.y + rect.height }, { x: rect.x, y: rect.y + rect.height } ]; for (let i = 0; i < points.length; i++) { canvasDrawLine(context, width, height, points[i], points[(i + 1) % points.length], "#FF3B58", mirrorHorizontal, mirrorVertical, cover); } } } function isScanBeepEnabled() { return getConfig("scanBeepEnabled") !== false; } function getScanBeepAudioSrc() { return getConfig("scanBeepAudio") || DEFAULT_SCAN_BEEP_AUDIO; } function resetScanBeepPlayback(audio) { try { audio.pause(); audio.currentTime = 0; } catch (e) { } } function installScanBeepGestureUnlock() { if (scanBeepGestureUnlockInstalled || typeof document === "undefined" || !isScanBeepEnabled()) { return; } scanBeepGestureUnlockInstalled = true; const onGesture = () => { unlockScanBeep(); }; document.addEventListener("click", onGesture, true); document.addEventListener("touchstart", onGesture, true); document.addEventListener("keydown", onGesture, true); } export function playScanBeep() { if (!isScanBeepEnabled()) { return; } const audio = getScanBeepAudio(); if (!audio) { return; } try { audio.muted = false; audio.volume = 1; audio.currentTime = 0; const playPromise = audio.play(); playPromise && playPromise.catch && playPromise.catch(err => { if (err && err.name === "NotAllowedError") { scanBeepUnlocked = false; } }); } catch (e) { } } function getScanBeepUnlockAudio() { if (typeof Audio === "undefined") { return null; } if (!scanBeepUnlockAudioEl) { scanBeepUnlockAudioEl = new Audio(SILENT_BEEP_UNLOCK_AUDIO); scanBeepUnlockAudioEl.preload = "auto"; scanBeepUnlockAudioEl.muted = true; scanBeepUnlockAudioEl.volume = 0; scanBeepUnlockAudioEl.setAttribute("playsinline", ""); try { scanBeepUnlockAudioEl.load && scanBeepUnlockAudioEl.load(); } catch (e) { } } return scanBeepUnlockAudioEl; } function getScanBeepAudio() { const audioSrc = getScanBeepAudioSrc(); if (!audioSrc || typeof Audio === 'undefined') { return null; } if (!scanBeepAudioEl || scanBeepAudioSrc !== audioSrc) { scanBeepAudioSrc = audioSrc; scanBeepUnlocked = false; scanBeepAudioEl = new Audio(audioSrc); scanBeepAudioEl.preload = "auto"; scanBeepAudioEl.setAttribute("playsinline", ""); try { scanBeepAudioEl.load && scanBeepAudioEl.load(); } catch (e) { } } installScanBeepGestureUnlock(); return scanBeepAudioEl; } export function unlockScanBeep() { if (!isScanBeepEnabled() || scanBeepUnlocked || scanBeepUnlocking) { return; } getScanBeepAudio(); const audio = getScanBeepUnlockAudio(); if (!audio) { return; } scanBeepUnlocking = true; try { audio.muted = true; audio.volume = 0; audio.currentTime = 0; const playPromise = audio.play(); const finishUnlock = () => { resetScanBeepPlayback(audio); scanBeepUnlocking = false; scanBeepUnlocked = true; }; if (!playPromise || !playPromise.then) { finishUnlock(); return; } playPromise.then(finishUnlock).catch(() => { resetScanBeepPlayback(audio); scanBeepUnlocking = false; }); } catch (e) { scanBeepUnlocking = false; } } export function isSupportWebScan() { return typeof navigator !== 'undefined' && navigator.mediaDevices && navigator.mediaDevices.getUserMedia && !!getBarcodeDetectorClass() && getConfig("webScanEnabled") !== false; } export function isSupportImageScan() { return typeof document !== 'undefined' && typeof URL !== 'undefined' && !!URL.createObjectURL; } export function isWebScanImageFallbackEnabled() { return isSupportImageScan() && getConfig("webScanImageFallbackOnVideoError") !== false; } function getWebScanVideoAccessTimeout() { const timeout = getConfig("webScanVideoAccessTimeout"); return typeof timeout === "number" && timeout > 0 ? timeout : 10000; } function getWebScanVideoReadyTimeout() { const timeout = getConfig("webScanVideoReadyTimeout"); return typeof timeout === "number" && timeout > 0 ? timeout : 8000; } function getUserMediaWithTimeout(constraints, timeoutMs) { return new Promise((resolve, reject) => { let settled = false; const timer = setTimeout(() => { if (settled) { return; } settled = true; reject(new Error("getUserMedia timeout")); }, timeoutMs); navigator.mediaDevices.getUserMedia(constraints).then(stream => { if (settled) { stopMediaStream(stream); return; } settled = true; clearTimeout(timer); resolve(stream); }).catch(err => { if (settled) { return; } settled = true; clearTimeout(timer); reject(err); }); }); } function runWebScanImageFallback(onResult, resolve, reject) { stopActiveWebScan(); unlockScanBeep(); return createBarcodeDetector(getConfig("webScanType")).then(detector => { return chooseImageFile({ preferCapture: isMobile() }).then(file => { if (!file) { return Promise.reject({ cancel: 1 }); } return detectImageFile(detector, file); }).then(code => { if (code && code.rawValue) { if (!onResult || !onResult(code.rawValue)) { return Promise.reject({ cancel: 1 }); } scanWeb.finish = true; resolve({ result: code.rawValue }); return; } return Promise.reject({ success: false, error: "未识别到二维码或条形码", imageFallbackUsed: true }); }); }).catch(err => { if (err && err.cancel) { reject({ cancel: 1, imageFallbackUsed: true }); return; } if (err && err.result) { reject(Object.assign({ imageFallbackUsed: true }, err)); return; } reject({ imageFallbackUsed: true, error: err && err.error ? err.error : err }); }); } function tryWebScanImageFallback(onResult, resolve, reject, state) { if (!isWebScanImageFallbackEnabled()) { return false; } if (state && state.imageFallbackStarted) { return false; } if (state) { state.imageFallbackStarted = true; } runWebScanImageFallback(onResult, resolve, reject); return true; } export function stopScanForWeb() { return Promise.resolve().then(() => { stopActiveWebScan(); }) } function chooseImageFile(options) { options = options || {}; return new Promise(resolve => { const input = document.createElement("input"); input.type = "file"; input.accept = "image/*"; if (options.preferCapture) { input.setAttribute("capture", options.captureMode || "environment"); } input.style.display = "none"; let finished = false; let finish = file => { if (finished) { return; } finished = true; removeEl("__webscan_image_input__"); resolve(file); }; input.id = "__webscan_image_input__"; input.onchange = () => { unlockScanBeep(); finish(input.files && input.files[0]); }; input.oncancel = () => { finish(null); }; document.body.appendChild(input); input.click(); }); } function detectImageFile(detector, file) { if (!file) { return Promise.resolve(null); } if (typeof createImageBitmap !== 'undefined') { return createImageBitmap(file).then(image => { return detector.detect(image).then(barcodes => { image.close && image.close(); return barcodes && barcodes[0]; }).catch(err => { image.close && image.close(); throw err; }); }); } return new Promise((resolve, reject) => { const image = new Image(); const url = URL.createObjectURL(file); image.onload = () => { detector.detect(image).then(barcodes => { URL.revokeObjectURL(url); resolve(barcodes && barcodes[0]); }).catch(err => { URL.revokeObjectURL(url); reject(err); }); }; image.onerror = err => { URL.revokeObjectURL(url); reject(err); }; image.src = url; }); } export function startScanForImage() { return chooseImageFile().then(file => { if (!file) { return null; } return createBarcodeDetector(getConfig("webScanType")).then(detector => { return detectImageFile(detector, file); }); }).then(code => { if (code && code.rawValue) { return { result: code.rawValue }; } return { success: false, error: "未识别到二维码或条形码" }; }); } export function startScanForWeb(onResult) { let currentUuid = null; const fallbackState = { imageFallbackStarted: false }; return new Promise((resolve, reject) => { try { stopActiveWebScan(); scanWeb.uuid = createUUID(); scanWeb.finish = false; currentUuid = scanWeb.uuid; let canvasStyle = getConfig("webScanCanvasStyle"); let canvasClass = getConfig("webScanCanvasClass"); let videoEl = createEl("video", "__webscan_video__", "display: none", false); let canvasEnabled = getConfig("webScanCanvasEnabled") !== false; let canvasDisplay = ""; let useDefaultCanvasLayout = !canvasStyle && !canvasClass; let defaultCanvasPx = useDefaultCanvasLayout ? getDefaultWebScanCanvasSizePx() : { width: 300, height: 300 }; let canvasFallbackWidth = defaultCanvasPx.width; let canvasFallbackHeight = defaultCanvasPx.height; let canvasBaseStyle = canvasStyle || (!!canvasClass ? "" : "position: fixed; width: " + defaultCanvasPx.width + "px; height: " + defaultCanvasPx.height + "px; top: 0; left: 0; z-index: 9999;"); let canvasEl = createEl("canvas", "__webscan_canvas__", canvasBaseStyle + " display: none;", true); canvasDisplay = canvasEl.style.display; canvasEl.style.cssText = canvasBaseStyle; canvasEl.className = canvasClass || ""; canvasDisplay = canvasEl.style.display; let canvasDisplaySize = getCanvasDisplaySize(canvasEl, canvasFallbackWidth, canvasFallbackHeight); canvasEl.style.display = "none"; let closeButtonEl = null; let pickImageButtonEl = null; if (canvasEnabled) { closeButtonEl = createEl("button", WEBSCAN_CLOSE_BUTTON_ID, "display: none;", true); closeButtonEl.type = "button"; closeButtonEl.className = getConfig("webScanCloseButtonClass") || ""; closeButtonEl.innerHTML = WEBSCAN_CLOSE_ICON_SVG; closeButtonEl.setAttribute("aria-label", "close"); closeButtonEl.onclick = event => { event.preventDefault && event.preventDefault(); event.stopPropagation && event.stopPropagation(); if (scanWeb.uuid === currentUuid) { scanWeb.uuid = null; } }; closeButtonEl.uuid = scanWeb.uuid; if (isSupportImageScan()) { pickImageButtonEl = createEl("button", WEBSCAN_PICK_IMAGE_BUTTON_ID, "display: none;", true); pickImageButtonEl.type = "button"; pickImageButtonEl.className = getConfig("webScanPickImageButtonClass") || ""; pickImageButtonEl.innerHTML = WEBSCAN_PICK_IMAGE_ICON_SVG; pickImageButtonEl.setAttribute("aria-label", "pick image"); pickImageButtonEl.uuid = scanWeb.uuid; } } let context = canvasEl.getContext("2d"); videoEl.width = 300; videoEl.height = 300; videoEl.uuid = scanWeb.uuid; canvasEl.uuid = scanWeb.uuid; createBarcodeDetector(getConfig("webScanType")).then(detector => { return getUserMediaWithTimeout({ video: { facingMode: "environment" } }, getWebScanVideoAccessTimeout()).then(stream => { return { detector, stream }; }); }).then(function (options) { const detector = options.detector; const stream = options.stream; if (scanWeb.uuid !== currentUuid) { stopMediaStream(stream); reject({ cancel: 1 }); return; } scanWeb.stream = stream; scanWeb.videoEl = videoEl; const mirrorVideo = shouldMirrorWebVideo(stream); const mirrorVideoVertical = shouldMirrorWebVideoVertical(); videoEl.srcObject = stream; videoEl.setAttribute("playsinline", true); // iOS使用 canvasEl.style.display = "none"; let detecting = false; let displayed = false; let closed = false; let videoReadyTimer = null; const clearVideoReadyTimer = () => { if (videoReadyTimer) { clearTimeout(videoReadyTimer); videoReadyTimer = null; } }; const scheduleVideoReadyTimeout = () => { clearVideoReadyTimer(); videoReadyTimer = setTimeout(() => { if (scanWeb.uuid !== currentUuid || closed || scanWeb.finish) { return; } if (videoEl.readyState >= videoEl.HAVE_ENOUGH_DATA) { return; } closed = true; close(); tryWebScanImageFallback(onResult, resolve, reject, fallbackState); }, getWebScanVideoReadyTimeout()); }; scheduleVideoReadyTimeout(); let close = () => { if (closed) { return; } closed = true; clearVideoReadyTimer(); stopMediaStream(stream); if (scanWeb.uuid === currentUuid || scanWeb.stream === stream) { scanWeb.stream = null; scanWeb.videoEl = null; } try { videoEl.pause && videoEl.pause(); videoEl.srcObject = null; } catch (_e) { } if (closeButtonEl) { closeButtonEl.style.display = "none"; } if (pickImageButtonEl) { pickImageButtonEl.style.display = "none"; } }; const videoPlayPromise = videoEl.play && videoEl.play(); if (videoPlayPromise && videoPlayPromise.catch) { videoPlayPromise.catch(() => { if (scanWeb.uuid !== currentUuid || closed || scanWeb.finish) { return; } close(); tryWebScanImageFallback(onResult, resolve, reject, fallbackState); }); } if (pickImageButtonEl) { pickImageButtonEl.onclick = event => { event.preventDefault && event.preventDefault(); event.stopPropagation && event.stopPropagation(); if (scanWeb.uuid !== currentUuid || closed) { return; } unlockScanBeep(); chooseImageFile().then(file => { if (!file || scanWeb.uuid !== currentUuid || closed) { return; } detectImageFile(detector, file).then(code => { if (!code || !code.rawValue || scanWeb.uuid !== currentUuid || closed) { return; } if (!onResult || !onResult(code.rawValue)) { return; } scanWeb.uuid = null; scanWeb.finish = true; close(); resolve({ result: code.rawValue }); }).catch(() => { }); }); }; } let tick = () => { try { if (videoEl.readyState === videoEl.HAVE_ENOUGH_DATA && !detecting) { clearVideoReadyTimer(); canvasEl.width = canvasDisplaySize.width; canvasEl.height = canvasDisplaySize.height; const cover = getCoverDrawOptions(videoEl.videoWidth, videoEl.videoHeight, canvasEl.width, canvasEl.height); context.setTransform( mirrorVideo ? -1 : 1, 0, 0, mirrorVideoVertical ? -1 : 1, mirrorVideo ? canvasEl.width : 0, mirrorVideoVertical ? canvasEl.height : 0 ); context.drawImage(videoEl, cover.x, cover.y, cover.width, cover.height); context.setTransform(1, 0, 0, 1, 0, 0); if (canvasEnabled && !displayed) { displayed = true; canvasEl.style.display = canvasDisplay || ""; canvasDisplaySize = getCanvasDisplaySize(canvasEl, canvasFallbackWidth, canvasFallbackHeight); updateWebScanCloseButtonStyle(closeButtonEl, canvasEl); closeButtonEl && (closeButtonEl.style.display = "flex"); if (pickImageButtonEl) { updateWebScanPickImageButtonStyle(pickImageButtonEl, canvasEl); pickImageButtonEl.style.display = "flex"; } } detecting = true; detector.detect(videoEl).then(barcodes => { const code = barcodes && barcodes[0]; if (code && code.rawValue && scanWeb.uuid == currentUuid) { if (!onResult || !onResult(code.rawValue)) { return; } drawBarcode(context, canvasEl.width, canvasEl.height, code, mirrorVideo, mirrorVideoVertical, cover); scanWeb.uuid = null; scanWeb.finish = true; close(); resolve({ result: code.rawValue }) } }).catch(() => { }).finally(() => { detecting = false; }); } } catch (e) { detecting = false; } if (scanWeb.uuid == currentUuid) { requestAnimationFrame(() => { tick() }); } else { if (!scanWeb.finish) { reject({ cancel: 1 }); scanWeb.finish = true; } close(); } if (scanWeb.finish) { close(); } }; requestAnimationFrame(() => { tick(); }); }).catch(err => { if (!tryWebScanImageFallback(onResult, resolve, reject, fallbackState)) { reject({ error: err }); } }); } catch (e) { reject({ error: e }); } }).finally(() => { removeEl("__webscan_video__", currentUuid); removeEl("__webscan_canvas__", currentUuid); removeEl(WEBSCAN_CLOSE_BUTTON_ID, currentUuid); removeEl(WEBSCAN_PICK_IMAGE_BUTTON_ID, currentUuid); }) }