UNPKG

ngx-scanner-qrcode

Version:

This library is built to provide a solution scanner QR code. This library takes in raw images and will locate, extract and parse any QR code found within.

1 lines 101 kB
{"version":3,"file":"ngx-scanner-qrcode.mjs","sources":["../../../projects/ngx-scanner-qrcode/src/lib/ngx-scanner-qrcode.options.ts","../../../projects/ngx-scanner-qrcode/src/lib/ngx-scanner-qrcode.default.ts","../../../projects/ngx-scanner-qrcode/src/lib/ngx-scanner-qrcode.helper.ts","../../../projects/ngx-scanner-qrcode/src/lib/ngx-scanner-qrcode.service.ts","../../../projects/ngx-scanner-qrcode/src/lib/ngx-scanner-qrcode.loader.ts","../../../projects/ngx-scanner-qrcode/src/lib/ngx-scanner-qrcode.component.ts","../../../projects/ngx-scanner-qrcode/src/public-api.ts","../../../projects/ngx-scanner-qrcode/src/ngx-scanner-qrcode.ts"],"sourcesContent":["export interface ScannerQRCodeConfig {\n src?: string;\n fps?: number;\n vibrate?: number; /** support mobile */\n decode?: string;\n unScan?: boolean;\n isBeep?: boolean;\n isMasked?: boolean;\n loadWasmUrl?: string;\n symbolType?: ScannerQRCodeSymbolType[];\n constraints?: MediaStreamConstraints;\n canvasStyles?: CanvasRenderingContext2D[] | any[];\n}\n\nexport interface ScannerQRCodeSelectedFiles {\n url: string;\n name: string;\n file: File;\n data?: ScannerQRCodeResult[];\n canvas?: HTMLCanvasElement;\n}\n\nexport interface ScannerQRCodeDevice {\n kind: string;\n label: string;\n groupId: string;\n deviceId: string;\n}\n\nexport interface ScannerQRCodePoint {\n x: number;\n y: number;\n}\n\nexport enum ScannerQRCodeSymbolType {\n ScannerQRCode_NONE = 0, /**< no symbol decoded */\n ScannerQRCode_PARTIAL = 1, /**< intermediate status */\n ScannerQRCode_EAN2 = 2, /**< GS1 2-digit add-on */\n ScannerQRCode_EAN5 = 5, /**< GS1 5-digit add-on */\n ScannerQRCode_EAN8 = 8, /**< EAN-8 */\n ScannerQRCode_UPCE = 9, /**< UPC-E */\n ScannerQRCode_ISBN10 = 10, /**< ISBN-10 (from EAN-13). @since 0.4 */\n ScannerQRCode_UPCA = 12, /**< UPC-A */\n ScannerQRCode_EAN13 = 13, /**< EAN-13 */\n ScannerQRCode_ISBN13 = 14, /**< ISBN-13 (from EAN-13). @since 0.4 */\n ScannerQRCode_COMPOSITE = 15, /**< EAN/UPC composite */\n ScannerQRCode_I25 = 25, /**< Interleaved 2 of 5. @since 0.4 */\n ScannerQRCode_DATABAR = 34, /**< GS1 DataBar (RSS). @since 0.11 */\n ScannerQRCode_DATABAR_EXP = 35, /**< GS1 DataBar Expanded. @since 0.11 */\n ScannerQRCode_CODABAR = 38, /**< Codabar. @since 0.11 */\n ScannerQRCode_CODE39 = 39, /**< Code 39. @since 0.4 */\n ScannerQRCode_PDF417 = 57, /**< PDF417. @since 0.6 */\n ScannerQRCode_QRCODE = 64, /**< QR Code. @since 0.10 */\n ScannerQRCode_SQCODE = 80, /**< SQ Code. @since 0.20.1 */\n ScannerQRCode_CODE93 = 93, /**< Code 93. @since 0.11 */\n ScannerQRCode_CODE128 = 128, /**< Code 128 */\n\n /*\n * Please see _ScannerQRCode_get_symbol_hash() if adding\n * anything after 128\n */\n\n /** mask for base symbol type.\n * @deprecated in 0.11, remove this from existing code\n */\n ScannerQRCode_SYMBOL = 0x00ff,\n /** 2-digit add-on flag.\n * @deprecated in 0.11, a ::ScannerQRCode_EAN2 component is used for\n * 2-digit GS1 add-ons\n */\n ScannerQRCode_ADDON2 = 0x0200,\n /** 5-digit add-on flag.\n * @deprecated in 0.11, a ::ScannerQRCode_EAN5 component is used for\n * 5-digit GS1 add-ons\n */\n ScannerQRCode_ADDON5 = 0x0500,\n /** add-on flag mask.\n * @deprecated in 0.11, GS1 add-ons are represented using composite\n * symbols of type ::ScannerQRCode_COMPOSITE; add-on components use ::ScannerQRCode_EAN2\n * or ::ScannerQRCode_EAN5\n */\n ScannerQRCode_ADDON = 0x0700,\n}\n\nexport enum ScannerQRCodeConfigType {\n ScannerQRCode_CFG_ENABLE = 0, /**< enable symbology/feature */\n ScannerQRCode_CFG_ADD_CHECK, /**< enable check digit when optional */\n ScannerQRCode_CFG_EMIT_CHECK, /**< return check digit when present */\n ScannerQRCode_CFG_ASCII, /**< enable full ASCII character set */\n ScannerQRCode_CFG_BINARY, /**< don't convert binary data to text */\n ScannerQRCode_CFG_NUM, /**< number of boolean decoder configs */\n\n ScannerQRCode_CFG_MIN_LEN = 0x20, /**< minimum data length for valid decode */\n ScannerQRCode_CFG_MAX_LEN, /**< maximum data length for valid decode */\n\n ScannerQRCode_CFG_UNCERTAINTY = 0x40, /**< required video consistency frames */\n\n ScannerQRCode_CFG_POSITION = 0x80, /**< enable scanner to collect position data */\n ScannerQRCode_CFG_TEST_INVERTED, /**< if fails to decode, test inverted */\n\n ScannerQRCode_CFG_X_DENSITY = 0x100, /**< image scanner vertical scan density */\n ScannerQRCode_CFG_Y_DENSITY, /**< image scanner horizontal scan density */\n}\n\nexport enum ScannerQRCodeOrientation {\n ScannerQRCode_ORIENT_UNKNOWN = -1, /**< unable to determine orientation */\n ScannerQRCode_ORIENT_UP, /**< upright, read left to right */\n ScannerQRCode_ORIENT_RIGHT, /**< sideways, read top to bottom */\n ScannerQRCode_ORIENT_DOWN, /**< upside-down, read right to left */\n ScannerQRCode_ORIENT_LEFT, /**< sideways, read bottom to top */\n}\n\nclass ScannerQRCodeTypePointer {\n protected ptr: number;\n protected ptr32: number;\n protected buf: ArrayBuffer;\n protected HEAP8: Int8Array;\n protected HEAP32: Int32Array;\n protected HEAPU32: Uint32Array;\n\n constructor(ptr: number, buf: ArrayBuffer) {\n this.ptr = ptr;\n this.ptr32 = ptr >> 2;\n this.buf = buf;\n this.HEAP8 = new Int8Array(buf);\n this.HEAPU32 = new Uint32Array(buf);\n this.HEAP32 = new Int32Array(buf);\n }\n}\n\nclass ScannerQRCodeSymbolPtr extends ScannerQRCodeTypePointer {\n get type(): ScannerQRCodeSymbolType {\n return this.HEAPU32[this.ptr32] as ScannerQRCodeSymbolType;\n }\n\n get data(): Int8Array {\n const len = this.HEAPU32[this.ptr32 + 4];\n const ptr = this.HEAPU32[this.ptr32 + 5];\n return Int8Array.from(this.HEAP8.subarray(ptr, ptr + len));\n }\n\n get points(): Array<ScannerQRCodePoint> {\n const len = this.HEAPU32[this.ptr32 + 7];\n const ptr = this.HEAPU32[this.ptr32 + 8];\n const ptr32 = ptr >> 2;\n const res: ScannerQRCodePoint[] = [];\n for (let i = 0; i < len; ++i) {\n const x = this.HEAP32[ptr32 + i * 2];\n const y = this.HEAP32[ptr32 + i * 2 + 1];\n res.push({ x, y } as ScannerQRCodePoint);\n }\n return res;\n }\n\n get orientation(): ScannerQRCodeOrientation {\n return this.HEAP32[this.ptr32 + 9];\n }\n\n get next(): ScannerQRCodeSymbolPtr | null {\n const ptr = this.HEAPU32[this.ptr32 + 11];\n if (!ptr) return null;\n return new ScannerQRCodeSymbolPtr(ptr, this.buf);\n }\n\n get time(): number {\n return this.HEAPU32[this.ptr32 + 13];\n }\n\n get cacheCount(): number {\n return this.HEAP32[this.ptr32 + 14];\n }\n\n get quality(): number {\n return this.HEAP32[this.ptr32 + 15];\n }\n}\n\nclass SymbolSetPtr extends ScannerQRCodeTypePointer {\n get head(): ScannerQRCodeSymbolPtr | null {\n const ptr = this.HEAPU32[this.ptr32 + 2];\n if (!ptr) return null;\n return new ScannerQRCodeSymbolPtr(ptr, this.buf);\n }\n}\n\nexport class ScannerQRCodeResult {\n type: ScannerQRCodeSymbolType;\n typeName: string;\n data: Int8Array;\n points: Array<ScannerQRCodePoint>;\n orientation: ScannerQRCodeOrientation;\n time: number;\n cacheCount: number;\n quality: number;\n value: string = '';\n\n private constructor(ptr: ScannerQRCodeSymbolPtr) {\n this.type = ptr.type;\n this.typeName = ScannerQRCodeSymbolType[this.type];\n this.data = ptr.data;\n this.points = ptr.points;\n this.orientation = ptr.orientation;\n this.time = ptr.time;\n this.cacheCount = ptr.cacheCount;\n this.quality = ptr.quality;\n }\n\n static createSymbolsFromPtr(ptr: number, buf: ArrayBuffer): Array<ScannerQRCodeResult> {\n if (ptr == 0) return [];\n\n const set = new SymbolSetPtr(ptr, buf);\n let symbol = set.head;\n const res: ScannerQRCodeResult[] = [];\n while (symbol !== null) {\n res.push(new ScannerQRCodeResult(symbol));\n symbol = symbol.next;\n }\n return res;\n }\n\n decode(encoding?: string) {\n const decoder = new TextDecoder(encoding);\n return decoder.decode(this.data);\n }\n}","import { ScannerQRCodeConfig, ScannerQRCodeSymbolType } from \"./ngx-scanner-qrcode.options\";\r\n\r\nexport const WASMPROJECT = \"assets/wasm/index.js\";\r\nexport const WASMREMOTE = \"https://cdn.jsdelivr.net/npm/ngx-scanner-qrcode@1.7.6/wasm/index.js\";\r\nexport const WASMREMOTELATEST = \"https://cdn.jsdelivr.net/npm/ngx-scanner-qrcode@latest/wasm/index.js\";\r\n\r\nexport const BEEP = `data:audio/mpeg;base64,SUQzBAAAAAAAI1RTU0UAAAAPAAADTGF2ZjU4LjI5LjEwMAAAAAAAAAAAAAAA/+M4wAAAAAAAAAAAAEluZm8AAAAPAAAABQAAAkAAgICAgICAgICAgICAgICAgICAgKCgoKCgoKCgoKCgoKCgoKCgoKCgwMDAwMDAwMDAwMDAwMDAwMDAwMDg4ODg4ODg4ODg4ODg4ODg4ODg4P//////////////////////////AAAAAExhdmM1OC41NAAAAAAAAAAAAAAAACQEUQAAAAAAAAJAk0uXRQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/+MYxAANQAbGeUEQAAHZYZ3fASqD4P5TKBgocg+Bw/8+CAYBA4XB9/4EBAEP4nB9+UOf/6gfUCAIKyjgQ/Kf//wfswAAAwQA/+MYxAYOqrbdkZGQAMA7DJLCsQxNOij///////////+tv///3RWiZGBEhsf/FO/+LoCSFs1dFVS/g8f/4Mhv0nhqAieHleLy/+MYxAYOOrbMAY2gABf/////////////////usPJ66R0wI4boY9/8jQYg//g2SPx1M0N3Z0kVJLIs///Uw4aMyvHJJYmPBYG/+MYxAgPMALBucAQAoGgaBoFQVBUFQWDv6gZBUFQVBUGgaBr5YSgqCoKhIGg7+IQVBUFQVBoGga//SsFSoKnf/iVTEFNRTMu/+MYxAYAAANIAAAAADEwMFVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV`;\r\n\r\nexport const MEDIA_STREAM_DEFAULT: MediaStreamConstraints = {\r\n audio: false,\r\n video: true\r\n};\r\n\r\nexport const CANVAS_STYLES_LAYER: CanvasRenderingContext2D | any = {\r\n lineWidth: 1,\r\n strokeStyle: 'green',\r\n fillStyle: '#55f02880'\r\n}\r\n\r\nexport const CANVAS_STYLES_TEXT: CanvasRenderingContext2D | any = {\r\n font: '15px serif',\r\n strokeStyle: '#fff0',\r\n fillStyle: '#ff0000'\r\n}\r\n\r\nexport const CONFIG_DEFAULT: ScannerQRCodeConfig = {\r\n src: '',\r\n fps: 30,\r\n vibrate: 300,\r\n decode: 'utf-8',\r\n unScan: false,\r\n isBeep: true,\r\n isMasked: true,\r\n loadWasmUrl: '',\r\n symbolType: [ScannerQRCodeSymbolType.ScannerQRCode_NONE],\r\n constraints: MEDIA_STREAM_DEFAULT,\r\n canvasStyles: [CANVAS_STYLES_LAYER, CANVAS_STYLES_TEXT]\r\n};","import { AsyncSubject } from \"rxjs\";\nimport { BEEP, CANVAS_STYLES_LAYER, CANVAS_STYLES_TEXT, CONFIG_DEFAULT } from \"./ngx-scanner-qrcode.default\";\nimport { ScannerQRCodeConfig, ScannerQRCodeSelectedFiles, ScannerQRCodeSymbolType } from \"./ngx-scanner-qrcode.options\";\ndeclare var zbarWasm: any;\n\n/**\n * WASM_READY\n * @returns \n */\nexport var WASM_READY = () => ('zbarWasm' in window);\n\n/**\n * OVERRIDES\n * @param variableKey \n * @param config \n * @param defaultConfig \n * @returns \n */\nexport const OVERRIDES = (variableKey: string, config: any, defaultConfig: any) => {\n if (config && Object.keys(config[variableKey]).length) {\n for (const key in defaultConfig) {\n const cloneDeep = JSON.parse(JSON.stringify({ ...config[variableKey], ...{ [key]: (defaultConfig as any)[key] } }));\n config[variableKey] = config[variableKey].hasOwnProperty(key) ? config[variableKey] : cloneDeep;\n }\n return config[variableKey];\n } else {\n return defaultConfig;\n }\n};\n\n/**\n * AS_COMPLETE\n * @param as \n * @param data \n * @param error \n */\nexport const AS_COMPLETE = (as: AsyncSubject<any>, data: any, error?: any) => {\n error ? as.error(error) : as.next(data);\n as.complete();\n};\n\n/**\n * PLAY_AUDIO\n * @param isPlay \n * @returns \n */\nexport const PLAY_AUDIO = (isPlay: boolean = false) => {\n if (isPlay === false) return;\n const audio = new Audio(BEEP);\n // when the sound has been loaded, execute your code\n audio.oncanplaythrough = () => {\n const promise = audio.play();\n if (promise) {\n promise.catch((e) => {\n if (e.name === \"NotAllowedError\" || e.name === \"NotSupportedError\") {\n // console.log(e.name);\n }\n });\n }\n };\n};\n\n/**\n * DRAW_RESULT_APPEND_CHILD\n * @param code \n * @param oriCanvas \n * @param elTarget \n * @param canvasStyles \n */\nexport const DRAW_RESULT_APPEND_CHILD = (code: any[], oriCanvas: HTMLCanvasElement, elTarget: HTMLCanvasElement | HTMLDivElement, canvasStyles: CanvasRenderingContext2D[]) => {\n let widthZoom;\n let heightZoom;\n let oriWidth = oriCanvas.width;\n let oriHeight = oriCanvas.height;\n let oriWHRatio = oriWidth / oriHeight;\n let imgWidth = parseInt(getComputedStyle(oriCanvas).width);\n let imgHeight = parseInt(getComputedStyle(oriCanvas).height);\n let imgWHRatio = imgWidth / imgHeight;\n elTarget.innerHTML = '';\n\n if (oriWHRatio > imgWHRatio) {\n widthZoom = imgWidth / oriWidth;\n heightZoom = imgWidth / oriWHRatio / oriHeight;\n } else {\n heightZoom = imgHeight / oriHeight;\n widthZoom = (imgHeight * oriWHRatio) / oriWidth;\n }\n\n for (let i = 0; i < code.length; i++) {\n const _code = code[i];\n // New canvas\n let cvs = document.createElement(\"canvas\");\n let ctx = cvs.getContext('2d', { willReadFrequently: true }) as CanvasRenderingContext2D;\n let loc: any = {};\n let X: any = [];\n let Y: any = [];\n let fontSize = 0;\n let svgSize = 0;\n\n let num = canvasStyles.length === 2 && canvasStyles[1]?.font?.replace(/[^0-9]/g, '');\n if (num && /[0-9]/g.test(num)) {\n fontSize = parseFloat(num);\n svgSize = (widthZoom || 1) * fontSize;\n if (Number.isNaN(svgSize)) {\n svgSize = fontSize;\n }\n }\n\n // Point x,y\n const points = _code.points;\n for (let j = 0; j < points.length; j++) {\n const xj = points?.[j]?.x ?? 0;\n const yj = points?.[j]?.y ?? 0;\n loc[`x${j + 1}`] = xj;\n loc[`y${j + 1}`] = yj;\n X.push(xj);\n Y.push(yj);\n }\n\n // Min max\n let maxX = Math.max(...X);\n let minX = Math.min(...X);\n let maxY = Math.max(...Y);\n let minY = Math.min(...Y);\n\n // Add class\n cvs.setAttribute('class', 'qrcode-polygon');\n\n // Size with screen zoom\n if (oriWHRatio > imgWHRatio) {\n cvs.style.top = minY * heightZoom + (imgHeight - imgWidth / oriWHRatio) * 0.5 + \"px\";\n cvs.style.left = minX * widthZoom + \"px\";\n cvs.width = (maxX - minX) * widthZoom;\n cvs.height = (maxY - minY) * widthZoom;\n } else {\n cvs.style.top = minY * heightZoom + \"px\";\n cvs.style.left = minX * widthZoom + (imgWidth - imgHeight * oriWHRatio) * 0.5 + \"px\";\n cvs.width = (maxX - minX) * heightZoom;\n cvs.height = (maxY - minY) * heightZoom;\n }\n\n // Style for canvas\n for (const key in canvasStyles[0]) {\n (ctx as any)[key] = (canvasStyles[0] as any)[key];\n }\n\n // polygon [x,y, x,y, x,y.....];\n const polygon = [];\n for (let k = 0; k < X.length; k++) {\n polygon.push((loc[`x${k + 1}`] - minX) * heightZoom);\n polygon.push((loc[`y${k + 1}`] - minY) * widthZoom);\n }\n\n // Copy array\n const shape = polygon.slice(0) as any;\n\n // Draw polygon\n ctx.beginPath();\n ctx.moveTo(shape.shift(), shape.shift());\n while (shape.length) {\n ctx.lineTo(shape.shift(), shape.shift()); //x,y\n }\n ctx.closePath();\n ctx.fill();\n ctx.stroke();\n\n if (fontSize) {\n // Tooltip result\n const qrcodeTooltipTemp = document.createElement('div');\n qrcodeTooltipTemp.setAttribute('class', 'qrcode-tooltip-temp');\n qrcodeTooltipTemp.innerText = _code.value;\n qrcodeTooltipTemp.style.maxWidth = ((oriWidth > window.innerWidth) ? window.innerWidth * 0.9 : oriWidth) + \"px\";\n qrcodeTooltipTemp.style.borderRadius = `clamp(1px, ${(widthZoom * fontSize) - 10}px, 3px)`;\n qrcodeTooltipTemp.style.paddingBlock = `clamp(1px, ${(widthZoom * fontSize) - 10}px, 3px)`; // top - bottom\n qrcodeTooltipTemp.style.paddingInline = `clamp(2.5px, ${(widthZoom * fontSize) - 6}px, 10px)`; // left - right\n const xmlString = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"${svgSize}\" height=\"${svgSize}\" viewBox=\"0 0 512 512\"><rect x=\"128\" y=\"128\" width=\"336\" height=\"336\" rx=\"57\" ry=\"57\"></rect><path d=\"M383.5,128l.5-24a56.16,56.16,0,0,0-56-56H112a64.19,64.19,0,0,0-64,64V328a56.16,56.16,0,0,0,56,56h24\"></path></svg>`;\n const xmlDom = new DOMParser().parseFromString(xmlString, 'application/xml');\n const svgDom = qrcodeTooltipTemp.ownerDocument.importNode(xmlDom.documentElement, true);\n svgDom.style.marginLeft = `clamp(1px, ${(widthZoom * fontSize) - 10}px, 3px)`; // left - right\n qrcodeTooltipTemp.appendChild(svgDom);\n svgDom.addEventListener(\"click\", () => window.navigator['clipboard'].writeText(_code.value));\n qrcodeTooltipTemp.addEventListener(\"click\", () => window.navigator['clipboard'].writeText(_code.value));\n\n // Tooltip box\n const qrcodeTooltip = document.createElement('div');\n qrcodeTooltip.setAttribute('class', 'qrcode-tooltip');\n qrcodeTooltip.appendChild(qrcodeTooltipTemp);\n heightZoom = imgHeight / oriHeight;\n widthZoom = (imgHeight * oriWHRatio) / oriWidth;\n qrcodeTooltip.style.fontSize = widthZoom * fontSize + 'px';\n qrcodeTooltip.style.top = minY * heightZoom + \"px\";\n qrcodeTooltip.style.left = minX * widthZoom + (imgWidth - imgHeight * oriWHRatio) * 0.5 + \"px\";\n qrcodeTooltip.style.width = (maxX - minX) * heightZoom + \"px\";\n qrcodeTooltip.style.height = (maxY - minY) * heightZoom + \"px\";\n\n // Result text\n const resultText = document.createElement('span');\n resultText.innerText = _code.value;\n\n // Set position result text\n resultText.style.top = minY * heightZoom + (-20 * heightZoom) + \"px\";\n resultText.style.left = minX * widthZoom + (imgWidth - imgHeight * oriWHRatio) * 0.5 + \"px\";\n\n // Style text\n const ff = canvasStyles[1]?.font?.split(' ')?.[1];\n resultText.style.fontFamily = ff;\n resultText.style.fontSize = widthZoom * fontSize + 'px';\n resultText.style.color = canvasStyles?.[1]?.fillStyle as string;\n\n elTarget?.appendChild(qrcodeTooltip);\n elTarget?.appendChild(resultText);\n }\n\n elTarget?.appendChild(cvs);\n };\n\n}\n\n/**\n * DRAW_RESULT_ON_CANVAS\n * @param code \n * @param cvs \n * @param canvasStyles \n */\nexport const DRAW_RESULT_ON_CANVAS = (code: any[], cvs: HTMLCanvasElement, canvasStyles: CanvasRenderingContext2D[]) => {\n let ctx = cvs.getContext('2d', { willReadFrequently: true }) as CanvasRenderingContext2D;\n\n for (let i = 0; i < code.length; i++) {\n const _code = code[i];\n let loc: any = {};\n let X: any = [];\n let Y: any = [];\n let fontSize = 0;\n\n const fs = canvasStyles[1]?.font?.split(' ')?.[0];\n let num = fs?.replace(/[^0-9]/g, '');\n if (num && /[0-9]/g.test(num)) {\n fontSize = parseFloat(num);\n }\n\n // Point x,y\n const points = _code.points;\n for (let j = 0; j < points.length; j++) {\n const xj = points?.[j]?.x ?? 0;\n const yj = points?.[j]?.y ?? 0;\n loc[`x${j + 1}`] = xj;\n loc[`y${j + 1}`] = yj;\n X.push(xj);\n Y.push(yj);\n }\n\n // Min max\n let minX = Math.min(...X);\n let minY = Math.min(...Y);\n\n const styleLayer = () => {\n for (const key in canvasStyles[0]) {\n (ctx as any)[key] = (canvasStyles[0] as any)[key];\n }\n\n // polygon [x,y, x,y, x,y.....];\n const polygon = [];\n for (let k = 0; k < X.length; k++) {\n polygon.push(loc[`x${k + 1}`]);\n polygon.push(loc[`y${k + 1}`]);\n }\n\n // Copy array\n const shape = polygon.slice(0) as any;\n\n // Draw polygon\n ctx.beginPath();\n ctx.moveTo(shape.shift(), shape.shift());\n while (shape.length) {\n ctx.lineTo(shape.shift(), shape.shift()); //x,y\n }\n ctx.closePath();\n ctx.fill();\n ctx.stroke();\n }\n\n let cvs2 = document.createElement('canvas');\n const styleText = () => {\n const ff = canvasStyles[1]?.font?.split(' ')?.[1];\n cvs2.height = cvs.height;\n cvs2.width = cvs.width;\n let ctx2 = cvs2.getContext('2d', { willReadFrequently: true }) as CanvasRenderingContext2D;\n ctx2.font = fontSize + `px ` + ff;\n for (const key in canvasStyles[1]) {\n (ctx2 as any)[key] = (canvasStyles[1] as any)[key];\n }\n FILL_TEXT_MULTI_LINE(ctx2, _code.value, minX, minY - 5);\n }\n\n styleLayer();\n styleText();\n // Merge cvs2 into cvs\n ctx.drawImage(cvs2, 0, 0);\n };\n}\n\n/**\n * READ_AS_DATA_URL\n * @param file \n * @param configs \n * @returns \n */\nexport const READ_AS_DATA_URL = (file: File, configs: ScannerQRCodeConfig): Promise<ScannerQRCodeSelectedFiles> => {\n /** overrides **/\n let decode = configs?.decode ?? CONFIG_DEFAULT.decode;\n let canvasStyles = configs?.canvasStyles?.length === 2 ? configs?.canvasStyles : [CANVAS_STYLES_LAYER, CANVAS_STYLES_TEXT];\n let isBeep = configs?.isBeep ?? CONFIG_DEFAULT.isBeep;\n let isMasked = configs?.isMasked ?? CONFIG_DEFAULT.isMasked;\n let unScan = configs?.unScan ?? CONFIG_DEFAULT.unScan;\n let symbolType = configs?.symbolType ?? CONFIG_DEFAULT.symbolType;\n\n /** drawImage **/\n return new Promise((resolve, reject) => {\n const fileReader = new FileReader();\n fileReader.onload = () => {\n const objectFile = {\n name: file.name,\n file: file,\n url: URL.createObjectURL(file)\n };\n // Set the src of this Image object.\n const image = new Image();\n // Setting cross origin value to anonymous\n image.setAttribute('crossOrigin', 'anonymous');\n // When our image has loaded.\n image.onload = async () => {\n // Get the canvas element by using the getElementById method.\n const canvas = document.createElement('canvas');\n // HTMLImageElement size\n canvas.width = image.naturalWidth || image.width;\n canvas.height = image.naturalHeight || image.height;\n // Get a 2D drawing context for the canvas.\n const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;\n // Draw image\n ctx.drawImage(image, 0, 0, canvas.width, canvas.height);\n // Data image\n const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);\n // Unscan\n if (unScan) {\n return resolve(Object.assign({}, objectFile, { data: [], canvas: canvas }));\n }\n // Scanner\n if (WASM_READY()) {\n let code = await zbarWasm.scanImageData(imageData);\n\n // SymbolType\n const hasSymbolType = symbolType?.some(s => s.toString() !== ScannerQRCodeSymbolType.ScannerQRCode_NONE.toString());\n if (hasSymbolType) {\n code = code.filter((s: any) => {\n const type = s.typeName.replace('ZBAR', 'ScannerQRCode');\n const valid = symbolType?.some(s => s.toString() === ScannerQRCodeSymbolType[type].toString());\n return valid;\n });\n }\n\n if (code?.length) {\n // Decode\n code.forEach((s: any) => s.value = s.decode(decode?.toLocaleLowerCase()));\n\n // Overlay\n if (isMasked) {\n DRAW_RESULT_ON_CANVAS(code, canvas, canvasStyles);\n }\n\n // Emit object\n const blob = await CANVAS_TO_BLOB(canvas);\n const url = URL.createObjectURL(blob);\n const blobToFile = (theBlob: any, fileName: string) => new File([theBlob], fileName, { lastModified: new Date().getTime(), type: theBlob.type });\n resolve(Object.assign({}, objectFile, { data: code, url: url, canvas: canvas, file: blobToFile(blob, objectFile.name) }));\n\n PLAY_AUDIO(isBeep);\n } else {\n resolve(Object.assign({}, objectFile, { data: code, canvas: canvas }));\n }\n }\n };\n // Set src\n image.src = objectFile.url;\n }\n fileReader.onerror = (error: any) => reject(error);\n fileReader.readAsDataURL(file);\n })\n}\n\n/**\n * Convert canvas to blob\n * canvas.toBlob((blob) => { .. }, 'image/jpeg', 0.95); // JPEG at 95% quality\n * @param canvas \n * @param type \n * @returns \n */\nexport const CANVAS_TO_BLOB = (canvas: HTMLCanvasElement, type?: string): Promise<any> => {\n return new Promise((resolve, reject) => canvas.toBlob(blob => resolve(blob), type));\n}\n\n/**\n * Convert blob to file\n * @param theBlob \n * @param fileName \n * @returns \n */\nexport const BLOB_TO_FILE = (theBlob: any, fileName: string): File => {\n return new File([theBlob], fileName, { lastModified: new Date().getTime(), type: theBlob.type });\n}\n\n/**\n * FILES_TO_SCAN\n * @param files \n * @param configs \n * @param percentage \n * @param quality \n * @param as \n * @returns \n */\nexport const FILES_TO_SCAN = (files: File[] = [], configs: ScannerQRCodeConfig, percentage?: number, quality?: number, as = new AsyncSubject<ScannerQRCodeSelectedFiles[]>()): AsyncSubject<ScannerQRCodeSelectedFiles[]> => {\n COMPRESS_IMAGE_FILE(files, percentage, quality).then((_files: File[]) => {\n Promise.all(Object.assign([], _files).map((m: File) => READ_AS_DATA_URL(m, configs))).then((img: ScannerQRCodeSelectedFiles[]) => {\n AS_COMPLETE(as, img);\n }).catch((error: any) => AS_COMPLETE(as, null, error));\n });\n return as;\n}\n\n/**\n * FILL_TEXT_MULTI_LINE\n * @param ctx \n * @param text \n * @param x \n * @param y \n */\nexport const FILL_TEXT_MULTI_LINE = (ctx: CanvasRenderingContext2D, text: string, x: number, y: number) => {\n let lineHeight = ctx.measureText(\"M\").width * 1.2;\n let lines = text.split(\"\\n\");\n for (var i = 0; i < lines.length; ++i) {\n ctx.fillText(lines[i], x, y);\n ctx.strokeText(lines[i], x, y);\n y += lineHeight;\n }\n}\n\n/**\n * COMPRESS_IMAGE_FILE\n * @param files \n * @param percentage \n * @param quality \n * @returns \n */\nexport const COMPRESS_IMAGE_FILE = (files: File[] = [], percentage = 100, quality = 100) => {\n if (files.length && (percentage < 100 || quality < 100)) {\n // Have files\n const resizedFiles: File[] = [];\n return new Promise<File[]>((resolve, reject) => {\n for (const file of files) {\n const image = new Image() as HTMLImageElement;\n const reader = new FileReader();\n reader.onload = function (event: any) {\n image.onload = function () {\n const canvas = document.createElement('canvas');\n const ctx = canvas.getContext('2d') as CanvasRenderingContext2D;\n const newWidth = Math.round(image.width * (percentage / 100));\n const newHeight = Math.round(image.height * (percentage / 100));\n canvas.width = newWidth;\n canvas.height = newHeight;\n ctx.drawImage(image, 0, 0, newWidth, newHeight);\n canvas.toBlob((blob: any) => {\n const resizedFile = new File([blob], file.name, { type: file.type });\n resizedFiles.push(resizedFile);\n if (files.length === resizedFiles.length) {\n resolve(resizedFiles);\n }\n }, file.type, quality / 100);\n };\n image.src = event.target.result;\n };\n reader.onerror = (error: any) => reject(error);\n reader.readAsDataURL(file);\n }\n });\n } else {\n // No files selected\n return Promise.resolve<File[]>(files);\n }\n}\n\n/**\n * REMOVE_RESULT_PANEL\n * @param element \n */\nexport const REMOVE_RESULT_PANEL = (element: HTMLElement) => {\n // clear text result and tooltip\n Object.assign([], element.childNodes).forEach(el => element.removeChild(el));\n}\n\n/**\n * RESET_CANVAS\n * @param canvas \n */\nexport const RESET_CANVAS = (canvas: HTMLCanvasElement) => {\n // reset canvas\n const context = canvas.getContext('2d', { willReadFrequently: true }) as CanvasRenderingContext2D;\n // clear frame when reloop\n context.clearRect(0, 0, canvas.width, canvas.height);\n}\n\n/**\n * UPDATE_WIDTH_HEIGHT_VIDEO\n * @param video \n * @param canvas \n */\nexport const UPDATE_WIDTH_HEIGHT_VIDEO = (video: HTMLVideoElement, canvas: HTMLCanvasElement): void => {\n video.style.width = canvas.offsetWidth + 'px';\n video.style.height = canvas.offsetHeight + 'px';\n}\n\n/**\n * VIBRATE\n * @param time \n */\nexport const VIBRATE = (time: number = 300) => {\n time && IS_MOBILE() && window?.navigator?.vibrate(time);\n};\n\n/**\n * IS_MOBILE\n * @returns \n */\nexport const IS_MOBILE = () => {\n const vendor = navigator.userAgent || navigator['vendor'] || (window as any)['opera'];\n const phone = /(android|bb\\d+|meego).+mobile|avantgo|bada\\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\\.(browser|link)|vodafone|wap|windows ce|xda|xiino|android|ipad|playbook|silk/i;\n const version = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw-(n|u)|c55\\/|capi|ccwa|cdm-|cell|chtm|cldc|cmd-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc-s|devi|dica|dmob|do(c|p)o|ds(12|-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(-|_)|g1 u|g560|gene|gf-5|g-mo|go(\\.w|od)|gr(ad|un)|haie|hcit|hd-(m|p|t)|hei-|hi(pt|ta)|hp( i|ip)|hs-c|ht(c(-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i-(20|go|ma)|i230|iac( |-|\\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\\/)|klon|kpt |kwc-|kyo(c|k)|le(no|xi)|lg( g|\\/(k|l|u)|50|54|-[a-w])|libw|lynx|m1-w|m3ga|m50\\/|ma(te|ui|xo)|mc(01|21|ca)|m-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|-([1-8]|c))|phil|pire|pl(ay|uc)|pn-2|po(ck|rt|se)|prox|psio|pt-g|qa-a|qc(07|12|21|32|60|-[2-7]|i-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h-|oo|p-)|sdk\\/|se(c(-|0|1)|47|mc|nd|ri)|sgh-|shar|sie(-|m)|sk-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h-|v-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl-|tdg-|tel(i|m)|tim-|t-mo|to(pl|sh)|ts(70|m-|m3|m5)|tx-9|up(\\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas-|your|zeto|zte-/i;\n const isSafari = /^((?!chrome|android).)*safari/i;\n return !!(phone.test(vendor) || version.test(vendor.substr(0, 4))) && !isSafari.test(vendor);\n};\n","import { Injectable } from '@angular/core';\nimport { AsyncSubject } from 'rxjs';\nimport { AS_COMPLETE, COMPRESS_IMAGE_FILE, FILES_TO_SCAN } from './ngx-scanner-qrcode.helper';\nimport { ScannerQRCodeConfig, ScannerQRCodeSelectedFiles } from './ngx-scanner-qrcode.options';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class NgxScannerQrcodeService {\n\n /**\n * loadFiles\n * @param files \n * @param percentage \n * @param quality \n * @returns \n */\n public loadFiles(files: File[] = [], percentage?: number, quality?: number): AsyncSubject<ScannerQRCodeSelectedFiles[]> {\n const as = new AsyncSubject<ScannerQRCodeSelectedFiles[]>();\n COMPRESS_IMAGE_FILE(files, percentage, quality).then((_files: File[]) => {\n Promise.all(Object.assign([], _files).map((m: File) => this.readAsDataURL(m))).then((img: ScannerQRCodeSelectedFiles[]) => AS_COMPLETE(as, img)).catch((error: any) => AS_COMPLETE(as, null, error));\n });\n return as;\n }\n\n /**\n * loadFilesToScan\n * @param files \n * @param config \n * @param percentage \n * @param quality \n * @returns \n */\n public loadFilesToScan(files: File[] = [], config: ScannerQRCodeConfig, percentage?: number, quality?: number): AsyncSubject<ScannerQRCodeSelectedFiles[]> {\n return FILES_TO_SCAN(files, config, percentage, quality);\n }\n\n /**\n * readAsDataURL\n * @param file \n * @returns \n */\n private readAsDataURL(file: File): Promise<ScannerQRCodeSelectedFiles> {\n /** drawImage **/\n return new Promise((resolve, reject) => {\n const fileReader = new FileReader();\n fileReader.onload = () => {\n const objectFile = {\n name: file.name,\n file: file,\n url: URL.createObjectURL(file)\n };\n resolve(objectFile);\n }\n fileReader.onerror = (error: any) => reject(error);\n fileReader.readAsDataURL(file);\n })\n }\n}","import { AsyncSubject } from \"rxjs\";\nimport { AS_COMPLETE, WASM_READY } from \"./ngx-scanner-qrcode.helper\";\nimport { Renderer2 } from \"@angular/core\";\nimport { WASMPROJECT, WASMREMOTE, WASMREMOTELATEST } from \"./ngx-scanner-qrcode.default\";\ndeclare var zbarWasm: any;\n\n/**\n * LOAD_WASM\n * @param loadWasmUrl \n * @param as \n * @param renderer \n * @returns \n */\nexport const LOAD_WASM = (loadWasmUrl = '', as: AsyncSubject<boolean | any> = new AsyncSubject(), renderer?: Renderer2): AsyncSubject<boolean | any> => {\n let retry = 0;\n const LOAD_WASM_RETRY = (isLoadWasmRemote = false) => {\n const DONE = (isFirst: boolean) => {\n let timeoutId: any;\n try {\n const END = () => {\n if (isFirst) {\n zbarWasm.setModuleArgs({ locateFile: (filename: string, directory: string) => loadWasmUrl ? loadWasmUrl : directory + filename });\n }\n setTimeout(() => AS_COMPLETE(as, true));\n };\n const L = () => {\n clearTimeout(timeoutId);\n WASM_READY() ? END() : timeoutId = setTimeout(() => L());\n }\n setTimeout(() => L());\n setTimeout(() => clearTimeout(timeoutId), 3000);\n } catch (error) {\n clearTimeout(timeoutId);\n }\n }\n const scriptRemote = (document.querySelectorAll(`script[src=\"${WASMREMOTE}\"]`) as any as Array<HTMLElement>);\n const scriptRemoteLatest = (document.querySelectorAll(`script[src=\"${WASMREMOTELATEST}\"]`) as any as Array<HTMLElement>);\n if (scriptRemote.length || scriptRemoteLatest.length) {\n DONE(false);\n } else {\n const scriptProject = (document.querySelectorAll(`script[src=\"${WASMPROJECT}\"]`) as any as Array<HTMLElement>);\n if (scriptProject.length === 1) {\n DONE(false);\n } else {\n scriptProject.forEach(f => f.remove());\n if (renderer) {\n const script = renderer.createElement(\"script\") as HTMLScriptElement;\n renderer.setAttribute(script, \"src\", isLoadWasmRemote ? WASMREMOTE : WASMPROJECT);\n renderer.setAttribute(script, \"type\", \"text/javascript\");\n renderer.setAttribute(script, \"async\", \"\");\n renderer.appendChild(document.head, script);\n script.onload = () => DONE(true);\n script.onerror = () => {\n if (retry < 2) {\n document.head.removeChild(script);\n LOAD_WASM_RETRY(true);\n } else {\n AS_COMPLETE(as, false, 'Could not load script ' + isLoadWasmRemote ? WASMREMOTE : WASMPROJECT);\n }\n }\n retry += 1;\n } else {\n const mod = document.createElement('script');\n mod.setAttribute(\"src\", isLoadWasmRemote ? WASMREMOTE : WASMPROJECT);\n mod.setAttribute(\"type\", \"text/javascript\");\n mod.setAttribute(\"async\", \"\");\n document.head.appendChild(mod);\n mod.onload = () => DONE(true);\n mod.onerror = () => {\n if (retry < 2) {\n document.head.removeChild(mod);\n LOAD_WASM_RETRY(true);\n } else {\n AS_COMPLETE(as, false, 'Could not load script ' + isLoadWasmRemote ? WASMREMOTE : WASMPROJECT);\n }\n }\n retry += 1;\n }\n }\n }\n }\n LOAD_WASM_RETRY();\n return as;\n}","import { Component, ElementRef, EventEmitter, Input, OnDestroy, OnInit, Output, Renderer2, ViewChild, ViewEncapsulation } from '@angular/core';\nimport { AsyncSubject, BehaviorSubject } from 'rxjs';\nimport { CANVAS_STYLES_LAYER, CANVAS_STYLES_TEXT, CONFIG_DEFAULT, MEDIA_STREAM_DEFAULT } from './ngx-scanner-qrcode.default';\nimport { AS_COMPLETE, BLOB_TO_FILE, CANVAS_TO_BLOB, DRAW_RESULT_APPEND_CHILD, FILES_TO_SCAN, OVERRIDES, PLAY_AUDIO, REMOVE_RESULT_PANEL, RESET_CANVAS, UPDATE_WIDTH_HEIGHT_VIDEO, VIBRATE, WASM_READY } from './ngx-scanner-qrcode.helper';\nimport { LOAD_WASM } from './ngx-scanner-qrcode.loader';\nimport { ScannerQRCodeConfig, ScannerQRCodeDevice, ScannerQRCodeResult, ScannerQRCodeSelectedFiles, ScannerQRCodeSymbolType } from './ngx-scanner-qrcode.options';\ndeclare var zbarWasm: any;\n\n@Component({\n selector: 'ngx-scanner-qrcode',\n template: `<div #resultsPanel class=\"origin-overlay\"></div><canvas #canvas class=\"origin-canvas\"></canvas><video #video playsinline class=\"origin-video\"></video>`,\n styleUrls: ['./ngx-scanner-qrcode.component.scss'],\n host: { 'class': 'ngx-scanner-qrcode' },\n exportAs: 'scanner',\n encapsulation: ViewEncapsulation.None\n})\nexport class NgxScannerQrcodeComponent implements OnInit, OnDestroy {\n\n /**\n * Element\n * playsinline required to tell iOS safari we don't want fullscreen\n */\n @ViewChild('video') public video!: ElementRef<HTMLVideoElement>;\n @ViewChild('canvas') public canvas!: ElementRef<HTMLCanvasElement>;\n @ViewChild('resultsPanel') public resultsPanel!: ElementRef<HTMLDivElement>;\n\n /**\n * EventEmitter\n */\n @Output() public event = new EventEmitter<ScannerQRCodeResult[]>();\n\n /**\n * Input\n */\n @Input() public config: ScannerQRCodeConfig = CONFIG_DEFAULT;\n @Input() public src: string | undefined = CONFIG_DEFAULT.src;\n @Input() public fps: number | undefined = CONFIG_DEFAULT.fps;\n @Input() public vibrate: number | undefined = CONFIG_DEFAULT.vibrate;\n @Input() public decode: string | undefined = CONFIG_DEFAULT.decode;\n @Input() public isBeep: boolean | undefined = CONFIG_DEFAULT.isBeep;\n @Input() public isMasked: boolean | undefined = CONFIG_DEFAULT.isMasked;\n @Input() public unScan: boolean | undefined = CONFIG_DEFAULT.unScan;\n @Input() public loadWasmUrl: string | undefined = CONFIG_DEFAULT.loadWasmUrl;\n @Input() public symbolType: ScannerQRCodeSymbolType[] | undefined = CONFIG_DEFAULT.symbolType;\n @Input() public constraints: MediaStreamConstraints | any = CONFIG_DEFAULT.constraints;\n @Input() public canvasStyles: CanvasRenderingContext2D[] | any[] = [CANVAS_STYLES_LAYER, CANVAS_STYLES_TEXT];\n\n /**\n * Export\n */\n public isStart: boolean = false;\n public isPause: boolean = false;\n public isLoading: boolean = false;\n public isTorch: boolean = false;\n public data = new BehaviorSubject<ScannerQRCodeResult[]>([]);\n public devices = new BehaviorSubject<ScannerQRCodeDevice[]>([]);\n public deviceIndexActive: number = 0;\n\n /**\n * Private\n */\n private rAF_ID: any;\n private dataForResize: ScannerQRCodeResult[] = [];\n private ready = new AsyncSubject<boolean>();\n\n private STATUS = {\n startON: () => this.isStart = true,\n pauseON: () => this.isPause = true,\n loadingON: () => this.isLoading = true,\n startOFF: () => this.isStart = false,\n pauseOFF: () => this.isPause = false,\n loadingOFF: () => this.isLoading = false,\n torchOFF: () => this.isTorch = false,\n }\n\n constructor(private renderer: Renderer2, private elementRef: ElementRef) { }\n\n ngOnInit(): void {\n this.overrideConfig();\n LOAD_WASM(this.loadWasmUrl, this.ready, this.renderer).subscribe(() => {\n if (this.src) {\n this.loadImage(this.src);\n }\n this.resize();\n });\n }\n\n /**\n * start\n * @param playDeviceCustom \n * @returns \n */\n public start(playDeviceCustom?: Function): AsyncSubject<any> {\n const as = new AsyncSubject<any>();\n if (this.isStart) {\n // Reject\n AS_COMPLETE(as, false);\n } else {\n // fix safari\n this.safariWebRTC(as, playDeviceCustom);\n }\n return as;\n }\n\n /**\n * stop\n * @returns \n */\n public stop(): AsyncSubject<any> {\n this.STATUS.pauseOFF();\n this.STATUS.startOFF();\n this.STATUS.torchOFF();\n this.STATUS.loadingOFF();\n const as = new AsyncSubject<any>();\n try {\n clearTimeout(this.rAF_ID);\n (this.video.nativeElement.srcObject as MediaStream).getTracks().forEach((track: MediaStreamTrack) => {\n track.stop();\n AS_COMPLETE(as, true);\n });\n this.dataForResize = [];\n RESET_CANVAS(this.canvas.nativeElement);\n REMOVE_RESULT_PANEL(this.resultsPanel.nativeElement);\n } catch (error) {\n AS_COMPLETE(as, false, error as any);\n }\n return as;\n }\n\n /**\n * play\n * @returns \n */\n public play(): AsyncSubject<any> {\n const as = new AsyncSubject<any>();\n if (this.isPause) {\n this.video.nativeElement.play();\n this.STATUS.pauseOFF();\n this.requestAnimationFrame();\n AS_COMPLETE(as, true);\n } else {\n AS_COMPLETE(as, false);\n }\n return as;\n }\n\n /**\n * pause\n * @returns \n */\n public pause(): AsyncSubject<any> {\n const as = new AsyncSubject<any>();\n if (this.isStart) {\n clearTimeout(this.rAF_ID);\n this.video.nativeElement.pause();\n this.STATUS.pauseON();\n AS_COMPLETE(as, true);\n } else {\n AS_COMPLETE(as, false);\n }\n return as;\n }\n\n /**\n * playDevice\n * @param deviceId \n * @param as \n * @returns \n */\n public playDevice(deviceId: string, as: AsyncSubject<any> = new AsyncSubject<any>()): AsyncSubject<any> {\n const constraints = this.getConstraints();\n const existDeviceId = (this.isStart && constraints) ? constraints.deviceId !== deviceId : true;\n switch (true) {\n case deviceId === 'null' || deviceId === 'undefined' || !deviceId:\n stop();\n this.stop();\n AS_COMPLETE(as, false);\n break;\n case deviceId && existDeviceId:\n stop();\n this.stop();\n // Loading on\n this.STATUS.loadingON();\n this.deviceIndexActive = this.devices.value.findIndex((f: ScannerQRCodeDevice) => f.deviceId === deviceId);\n const constraints = { ...this.constraints, audio: false, video: { deviceId: deviceId, ...this.constraints.video } };\n // MediaStream\n navigator.mediaDevices.getUserMedia(constraints).then((stream: MediaStream) => {\n this.video.nativeElement.srcObject = stream;\n this.video.nativeElement.onloadedmetadata = () => {\n this.video.nativeElement.play();\n this.requestAnimationFrame();\n AS_COMPLETE(as, true);\n this.STATUS.startON();\n this.STATUS.loadingOFF();\n }\n }).catch((error: any) => {\n this.eventEmit(false);\n AS_COMPLETE(as, false, error);\n this.STATUS.startOFF();\n this.STATUS.loadingOFF();\n });\n break;\n default:\n AS_COMPLETE(as, false);\n this.STATUS.loadingOFF();\n break;\n }\n return as;\n }\n\n /**\n * loadImage\n * @param src \n * @returns \n */\n public loadImage(src: string): AsyncSubject<any> {\n const as = new AsyncSubject<any>();\n // Loading on\n this.STATUS.startOFF();\n this.STATUS.loadingON();\n // Set the src of this Image object.\n const image = new Image();\n // Setting cross origin value to anonymous\n image.setAttribute('crossOrigin', 'anonymous');\n // When our image has loaded.\n image.onload = () => {\n WASM_READY() && this.drawImage(image, (flag: boolean) => {\n AS_COMPLETE(as, flag);\n this.STATUS.startOFF();\n this.STATUS.loadingOFF();\n });\n };\n // Set src\n image.src = src;\n return as;\n }\n\n /**\n * torcher\n * @returns \n */\n public torcher(): AsyncSubject<any> {\n const as = this.applyConstraints({ advanced: [{ torch: this.isTorch }] });\n as.subscribe(() => false, () => this.isTorch = !this.isTorch);\n return as;\n }\n\n /**\n * applyConstraints\n * @param constraints \n * @param deviceIndex \n * @returns \n */\n public applyConstraints(constraints: MediaTrackConstraintSet | MediaTrackConstraints | any, deviceIndex = 0): AsyncSubject<any> {\n const as = new AsyncSubject<any>();\n if (this.isStart) {\n const stream = this.video.nativeElement.srcObject as MediaStream;\n if (deviceIndex !== null || deviceIndex !== undefined || !Number.isNaN(deviceIndex)) {\n const videoTrack = stream.getVideoTracks()[deviceIndex] as MediaStreamTrack;\n const imageCapture = new (window as any).ImageCapture(videoTrack);\n imageCapture.getPhotoCapabilities().then(async () => {\n await videoTrack.applyConstraints(constraints);\n UPDATE_WIDTH_HEIGHT_VIDEO(this.video.nativeElement, this.canvas.nativeElement);\n AS_COMPLETE(as, true);\n }).catch((error: any) => {\n switch (error?.name) {\n case 'NotFoundError':\n case 'DevicesNotFoundError':\n AS_COMPLETE(as, false, 'Required track is missing' as string);\n break;\n case 'NotReadableError':\n case 'TrackStartError':\n AS_COMPLETE(as, false, 'Webcam or mic are already in use' as string);\n break;\n case 'OverconstrainedError':\n case 'ConstraintNotSatisfiedError':\n AS_COMPLETE(as, false, 'Constraints can not be satisfied by avb. devices' as string);\n break;\n case 'NotAllowedError':\n case 'PermissionDeniedError':\n AS_COMPLETE(as, false, 'Permission denied in browser' as string);\n break;\n case 'TypeError':\n AS_COMPLETE(as, false, 'Empty constraints object' as string);\n break;\n default:\n AS_COMPLETE(as, false, error as any);\n break;\n }\n });\n } else {\n AS_COMPLETE(as, false, 'Please check again deviceIndex' as string);\n }\n } else {\n AS_COMPLETE(as, false, 'Please start the scanner' as string);\n }\n return as;\n };\n\n /**\n * getConstraints\n * @param deviceIndex \n * @returns \n */\n public getConstraints(deviceIndex = 0): MediaTrackConstraintSet | MediaTrackConstraints {\n const stream = this.video.nativeElement.srcObject as MediaStream;\n const videoTrack = stream?.getVideoTracks()[deviceIndex] as MediaStreamTrack;\n return videoTrack?.getConstraints() as MediaTrackConstraints;\n }\n\n /**\n * download\n * @param fileName \n * @param percentage \n * @param quality \n * @returns \n */\n public download(fileName: string = `ngx_scanner_qrcode_${Date.now()}.png`, percentage?: number, quality?: number): AsyncSubject<ScannerQRCodeSelectedFiles[]> {\n const as = new AsyncSubject<any>();\n (async () => {\n const blob = await CANVAS_TO_BLOB(this.canvas.nativeElement);\n const file = BLOB_TO_FILE(blob, fileName);\n FILES_TO_SCAN([file], this.config, percentage, quality, as).subscribe((res: ScannerQRCodeSelectedFiles[]) => {\n res.forEach((item: ScannerQRCodeSelectedFiles) => {\n if (item?.data?.length) {\n const link = document.createElement('a');\n link.href = item.url;\n link.download = item.name;\n link.click();\n link.remove();\n }\n });\n });\n })();\n return as;\n }\n\n /**\n * resize\n */\n private resize(): void {\n window.addEventListener(\"resize\", () => {\n DRAW_RESULT_APPEND_CHILD(this.dataForResize as any, this.canvas.nativeElement, this.resultsPanel.nativeElement, this.canvasStyles);\n UPDATE_WIDTH_HEIGHT_VIDEO(this.video.nativeElement, this.canvas.nativeElement);\n });\n }\n\n /**\n * overrideConfig\n */\n private overrideConfig(): void {\n if ('src' in this.config) this.src = this.config.src;\n if ('fps' in this.config) this.fps = this.config.fps;\n if ('vibrate' in this.config) this.vibrate = this.config.vibrate;\n if ('decode' in this.config) this.decode = this.config.decode;\n if ('isBeep' in this.config) this.isBeep = this.config.isBeep;\n if ('isMasked' in this.config) this.isMasked = this.config.isMasked;\n if ('unScan' in this.config) this.unScan = this.config.unScan;\n if ('loadWasmUrl' in this.config) this.loadWasmUrl = this.config.loadWasmUrl;\n if ('symbolType' in this.config) this.symbolType = this.config.symbolType;\n if ('constraints' in this.config) this.constraints = OVERRIDES('constraints', this.config, MEDIA_STREAM_DEFAULT);\n if ('canvasStyles' in this.config && this.config?.canvasStyles?.length === 2) this.canvasStyles = this.config.canvasStyles;\n }\n\n /**\n * safariWebRTC\n * Fix issue on safari\n * https://webrtchacks.com/guide-to-safari-webrtc\n * @param