Intégration scan QR Code.
This commit is contained in:
parent
c06aec8c32
commit
dcc512876d
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
localhost-key.pem
|
||||
localhost.pem
|
@ -1,3 +1,7 @@
|
||||
# Chasse au trésor Lycée Pange
|
||||
|
||||
**Auteur :** Antoine WAEHREN
|
||||
|
||||
## Crédits
|
||||
|
||||
Ce projet utilise le module QR Scanner de NIMIQ : https://github.com/nimiq/qr-scanner
|
36
index.php
36
index.php
@ -1,13 +1,5 @@
|
||||
<?php
|
||||
require_once "require/base.php";
|
||||
|
||||
$art_nb = 3;
|
||||
|
||||
$database = new Database();
|
||||
$stmt = $database->pdo_article->prepare("SELECT * FROM article WHERE id <= :id");
|
||||
$stmt->bindValue(":id", $art_nb);
|
||||
$stmt->execute();
|
||||
$articles = $stmt->fetchAll();
|
||||
?>
|
||||
|
||||
<!DOCTYPE html>
|
||||
@ -17,6 +9,7 @@
|
||||
require_once $rq_path . "head.php";
|
||||
?>
|
||||
<title><?= $tr["tab_title"]["home"]?></title>
|
||||
<script type="module" src="<?= $rq_path ?>js/qrscan.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
@ -30,23 +23,18 @@
|
||||
<p><?= $tr["home"]["subtitle"]?></p>
|
||||
<p><?= $tr["home"]["message"]?></p>
|
||||
</header>
|
||||
<nav>
|
||||
<article>
|
||||
<button>Scanner un QR code</button>
|
||||
<p></p>
|
||||
<video></video>
|
||||
<script src="<?= $rq_path ?>js/qr-scanner/qr-scanner.umd.min.js"></script>
|
||||
<script>
|
||||
const qrScanner = new QrScanner(
|
||||
document.querySelector("video"),
|
||||
result => console.log('decoded qr code:', result),
|
||||
{
|
||||
onDecodeError: error => {
|
||||
},
|
||||
highlightScanRegion: true,
|
||||
highlightCodeOutline: true,
|
||||
},
|
||||
);
|
||||
qrScanner.start();
|
||||
</script>
|
||||
</nav>
|
||||
<p>Caméra sélectionnée :</p>
|
||||
<select>
|
||||
<option value="rear" selected>Caméra arrière</option>
|
||||
<option value="front">Caméra avant</option>
|
||||
</select> <!-- Emplacement du message d'erreur JS concernant la caméra. !-->
|
||||
<p>Scanner depuis un fichier</p>
|
||||
<input type="file" id="file-selector">
|
||||
</article>
|
||||
</main>
|
||||
<footer>
|
||||
<?php
|
||||
|
12
require/js/qr-scanner/qr-scanner-worker.min.js
vendored
12
require/js/qr-scanner/qr-scanner-worker.min.js
vendored
@ -1,4 +1,8 @@
|
||||
export const createWorker = () => new Worker(URL.createObjectURL(new Blob([`class x{constructor(a,b){this.width=b;this.height=a.length/b;this.data=a}static createEmpty(a,b){return new x(new Uint8ClampedArray(a*b),a)}get(a,b){return 0>a||a>=this.width||0>b||b>=this.height?!1:!!this.data[b*this.width+a]}set(a,b,c){this.data[b*this.width+a]=c?1:0}setRegion(a,b,c,d,e){for(let f=b;f<b+d;f++)for(let g=a;g<a+c;g++)this.set(g,f,!!e)}}
|
||||
export const createWorker = () =>
|
||||
new Worker(
|
||||
URL.createObjectURL(
|
||||
new Blob([
|
||||
`class x{constructor(a,b){this.width=b;this.height=a.length/b;this.data=a}static createEmpty(a,b){return new x(new Uint8ClampedArray(a*b),a)}get(a,b){return 0>a||a>=this.width||0>b||b>=this.height?!1:!!this.data[b*this.width+a]}set(a,b,c){this.data[b*this.width+a]=c?1:0}setRegion(a,b,c,d,e){for(let f=b;f<b+d;f++)for(let g=a;g<a+c;g++)this.set(g,f,!!e)}}
|
||||
class A{constructor(a,b,c){this.width=a;a*=b;if(c&&c.length!==a)throw Error("Wrong buffer size");this.data=c||new Uint8ClampedArray(a)}get(a,b){return this.data[b*this.width+a]}set(a,b,c){this.data[b*this.width+a]=c}}
|
||||
class ba{constructor(a){this.bitOffset=this.byteOffset=0;this.bytes=a}readBits(a){if(1>a||32<a||a>this.available())throw Error("Cannot read "+a.toString()+" bits");var b=0;if(0<this.bitOffset){b=8-this.bitOffset;var c=a<b?a:b;b-=c;b=(this.bytes[this.byteOffset]&255>>8-c<<b)>>b;a-=c;this.bitOffset+=c;8===this.bitOffset&&(this.bitOffset=0,this.byteOffset++)}if(0<a){for(;8<=a;)b=b<<8|this.bytes[this.byteOffset]&255,this.byteOffset++,a-=8;0<a&&(c=8-a,b=b<<a|(this.bytes[this.byteOffset]&255>>c<<c)>>c,
|
||||
this.bitOffset+=a)}return b}available(){return 8*(this.bytes.length-this.byteOffset)-this.bitOffset}}var B,C=B||(B={});C.Numeric="numeric";C.Alphanumeric="alphanumeric";C.Byte="byte";C.Kanji="kanji";C.ECI="eci";C.StructuredAppend="structuredappend";var D,E=D||(D={});E[E.Terminator=0]="Terminator";E[E.Numeric=1]="Numeric";E[E.Alphanumeric=2]="Alphanumeric";E[E.Byte=4]="Byte";E[E.Kanji=8]="Kanji";E[E.ECI=7]="ECI";E[E.StructuredAppend=3]="StructuredAppend";let F="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:".split("");
|
||||
@ -95,4 +99,8 @@ u.set(r,q,v)}h?(q=new Uint8ClampedArray(a.buffer,m,k),m+=k,q=new x(q,b)):q=x.cre
|
||||
y:z))||"attemptBoth"!==e.inversionAttempts&&"invertFirst"!==e.inversionAttempts||(f=V(d?z:y));return f}X.default=X;let Y="dontInvert",Z={red:77,green:150,blue:29,useIntegerApproximation:!0};
|
||||
self.onmessage=a=>{let b=a.data.id,c=a.data.data;switch(a.data.type){case "decode":(a=X(c.data,c.width,c.height,{inversionAttempts:Y,greyScaleWeights:Z}))?self.postMessage({id:b,type:"qrResult",data:a.data,cornerPoints:[a.location.topLeftCorner,a.location.topRightCorner,a.location.bottomRightCorner,a.location.bottomLeftCorner]}):self.postMessage({id:b,type:"qrResult",data:null});break;case "grayscaleWeights":Z.red=c.red;Z.green=c.green;Z.blue=c.blue;Z.useIntegerApproximation=c.useIntegerApproximation;
|
||||
break;case "inversionMode":switch(c){case "original":Y="dontInvert";break;case "invert":Y="onlyInvert";break;case "both":Y="attemptBoth";break;default:throw Error("Invalid inversion mode");}break;case "close":self.close()}}
|
||||
`]), { type: "application/javascript" }))//# sourceMappingURL=qr-scanner-worker.min.js.map
|
||||
`,
|
||||
]),
|
||||
{ type: "application/javascript" }
|
||||
)
|
||||
); //# sourceMappingURL=qr-scanner-worker.min.js.map
|
||||
|
1
require/js/qr-scanner/qr-scanner-worker.min.js.map
Normal file
1
require/js/qr-scanner/qr-scanner-worker.min.js.map
Normal file
File diff suppressed because one or more lines are too long
749
require/js/qr-scanner/qr-scanner.min.js
vendored
749
require/js/qr-scanner/qr-scanner.min.js
vendored
@ -1,103 +1,650 @@
|
||||
class e {
|
||||
constructor(a, b, c, d, f) {
|
||||
this._legacyCanvasSize = e.DEFAULT_CANVAS_SIZE; this._preferredCamera = "environment"; this._maxScansPerSecond = 25; this._lastScanTimestamp = -1; this._destroyed = this._flashOn = this._paused = this._active = !1; this.$video = a; this.$canvas = document.createElement("canvas"); c && "object" === typeof c ? this._onDecode = b : (c || d || f ? console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future") : console.warn("Note that the type of the scan result passed to onDecode will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true."),
|
||||
this._legacyOnDecode = b); b = "object" === typeof c ? c : {}; this._onDecodeError = b.onDecodeError || ("function" === typeof c ? c : this._onDecodeError); this._calculateScanRegion = b.calculateScanRegion || ("function" === typeof d ? d : this._calculateScanRegion); this._preferredCamera = b.preferredCamera || f || this._preferredCamera; this._legacyCanvasSize = "number" === typeof c ? c : "number" === typeof d ? d : this._legacyCanvasSize; this._maxScansPerSecond = b.maxScansPerSecond || this._maxScansPerSecond; this._onPlay = this._onPlay.bind(this); this._onLoadedMetaData =
|
||||
this._onLoadedMetaData.bind(this); this._onVisibilityChange = this._onVisibilityChange.bind(this); this._updateOverlay = this._updateOverlay.bind(this); a.disablePictureInPicture = !0; a.playsInline = !0; a.muted = !0; let h = !1; a.hidden && (a.hidden = !1, h = !0); document.body.contains(a) || (document.body.appendChild(a), h = !0); c = a.parentElement; if (b.highlightScanRegion || b.highlightCodeOutline) {
|
||||
d = !!b.overlay; this.$overlay = b.overlay || document.createElement("div"); f = this.$overlay.style; f.position = "absolute"; f.display = "none";
|
||||
f.pointerEvents = "none"; this.$overlay.classList.add("scan-region-highlight"); if (!d && b.highlightScanRegion) {
|
||||
this.$overlay.innerHTML = '<svg class="scan-region-highlight-svg" viewBox="0 0 238 238" preserveAspectRatio="none" style="position:absolute;width:100%;height:100%;left:0;top:0;fill:none;stroke:#e9b213;stroke-width:4;stroke-linecap:round;stroke-linejoin:round"><path d="M31 2H10a8 8 0 0 0-8 8v21M207 2h21a8 8 0 0 1 8 8v21m0 176v21a8 8 0 0 1-8 8h-21m-176 0H10a8 8 0 0 1-8-8v-21"/></svg>'; try {
|
||||
this.$overlay.firstElementChild.animate({
|
||||
transform: ["scale(.98)",
|
||||
"scale(1.01)"]
|
||||
}, { duration: 400, iterations: Infinity, direction: "alternate", easing: "ease-in-out" })
|
||||
} catch (m) { } c.insertBefore(this.$overlay, this.$video.nextSibling)
|
||||
} b.highlightCodeOutline && (this.$overlay.insertAdjacentHTML("beforeend", '<svg class="code-outline-highlight" preserveAspectRatio="none" style="display:none;width:100%;height:100%;fill:none;stroke:#e9b213;stroke-width:5;stroke-dasharray:25;stroke-linecap:round;stroke-linejoin:round"><polygon/></svg>'), this.$codeOutlineHighlight = this.$overlay.lastElementChild)
|
||||
} this._scanRegion =
|
||||
this._calculateScanRegion(a); requestAnimationFrame(() => {
|
||||
let m = window.getComputedStyle(a); "none" === m.display && (a.style.setProperty("display", "block", "important"), h = !0); "visible" !== m.visibility && (a.style.setProperty("visibility", "visible", "important"), h = !0); h && (console.warn("QrScanner has overwritten the video hiding style to avoid Safari stopping the playback."), a.style.opacity = "0", a.style.width = "0", a.style.height = "0", this.$overlay && this.$overlay.parentElement && this.$overlay.parentElement.removeChild(this.$overlay),
|
||||
delete this.$overlay, delete this.$codeOutlineHighlight); this.$overlay && this._updateOverlay()
|
||||
}); a.addEventListener("play", this._onPlay); a.addEventListener("loadedmetadata", this._onLoadedMetaData); document.addEventListener("visibilitychange", this._onVisibilityChange); window.addEventListener("resize", this._updateOverlay); this._qrEnginePromise = e.createQrEngine()
|
||||
} static set WORKER_PATH(a) { console.warn("Setting QrScanner.WORKER_PATH is not required and not supported anymore. Have a look at the README for new setup instructions.") } static async hasCamera() { try { return !!(await e.listCameras(!1)).length } catch (a) { return !1 } } static async listCameras(a =
|
||||
!1) { if (!navigator.mediaDevices) return []; let b = async () => (await navigator.mediaDevices.enumerateDevices()).filter(d => "videoinput" === d.kind), c; try { a && (await b()).every(d => !d.label) && (c = await navigator.mediaDevices.getUserMedia({ audio: !1, video: !0 })) } catch (d) { } try { return (await b()).map((d, f) => ({ id: d.deviceId, label: d.label || (0 === f ? "Default Camera" : `Camera ${f + 1}`) })) } finally { c && (console.warn("Call listCameras after successfully starting a QR scanner to avoid creating a temporary video stream"), e._stopVideoStream(c)) } } async hasFlash() {
|
||||
let a;
|
||||
try { if (this.$video.srcObject) { if (!(this.$video.srcObject instanceof MediaStream)) return !1; a = this.$video.srcObject } else a = (await this._getCameraStream()).stream; return "torch" in a.getVideoTracks()[0].getSettings() } catch (b) { return !1 } finally { a && a !== this.$video.srcObject && (console.warn("Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream"), e._stopVideoStream(a)) }
|
||||
} isFlashOn() { return this._flashOn } async toggleFlash() { this._flashOn ? await this.turnFlashOff() : await this.turnFlashOn() } async turnFlashOn() {
|
||||
if (!this._flashOn &&
|
||||
!this._destroyed && (this._flashOn = !0, this._active && !this._paused)) try { if (!await this.hasFlash()) throw "No flash available"; await this.$video.srcObject.getVideoTracks()[0].applyConstraints({ advanced: [{ torch: !0 }] }) } catch (a) { throw this._flashOn = !1, a; }
|
||||
} async turnFlashOff() { this._flashOn && (this._flashOn = !1, await this._restartVideoStream()) } destroy() {
|
||||
this.$video.removeEventListener("loadedmetadata", this._onLoadedMetaData); this.$video.removeEventListener("play", this._onPlay); document.removeEventListener("visibilitychange",
|
||||
this._onVisibilityChange); window.removeEventListener("resize", this._updateOverlay); this._destroyed = !0; this._flashOn = !1; this.stop(); e._postWorkerMessage(this._qrEnginePromise, "close")
|
||||
} async start() {
|
||||
if (this._destroyed) throw Error("The QR scanner can not be started as it had been destroyed."); if (!this._active || this._paused) if ("https:" !== window.location.protocol && console.warn("The camera stream is only accessible if the page is transferred via https."), this._active = !0, !document.hidden) if (this._paused =
|
||||
!1, this.$video.srcObject) await this.$video.play(); else try { let { stream: a, facingMode: b } = await this._getCameraStream(); !this._active || this._paused ? e._stopVideoStream(a) : (this._setVideoMirror(b), this.$video.srcObject = a, await this.$video.play(), this._flashOn && (this._flashOn = !1, this.turnFlashOn().catch(() => { }))) } catch (a) { if (!this._paused) throw this._active = !1, a; }
|
||||
} stop() { this.pause(); this._active = !1 } async pause(a = !1) {
|
||||
this._paused = !0; if (!this._active) return !0; this.$video.pause(); this.$overlay && (this.$overlay.style.display =
|
||||
"none"); let b = () => { this.$video.srcObject instanceof MediaStream && (e._stopVideoStream(this.$video.srcObject), this.$video.srcObject = null) }; if (a) return b(), !0; await new Promise(c => setTimeout(c, 300)); if (!this._paused) return !1; b(); return !0
|
||||
} async setCamera(a) { a !== this._preferredCamera && (this._preferredCamera = a, await this._restartVideoStream()) } static async scanImage(a, b, c, d, f = !1, h = !1) {
|
||||
let m, n = !1; b && ("scanRegion" in b || "qrEngine" in b || "canvas" in b || "disallowCanvasResizing" in b || "alsoTryWithoutScanRegion" in
|
||||
b || "returnDetailedScanResult" in b) ? (m = b.scanRegion, c = b.qrEngine, d = b.canvas, f = b.disallowCanvasResizing || !1, h = b.alsoTryWithoutScanRegion || !1, n = !0) : b || c || d || f || h ? console.warn("You're using a deprecated api for scanImage which will be removed in the future.") : console.warn("Note that the return type of scanImage will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true."); b = !!c; try {
|
||||
let p, k;[c, p] = await Promise.all([c || e.createQrEngine(), e._loadImage(a)]);
|
||||
[d, k] = e._drawToCanvas(p, m, d, f); let q; if (c instanceof Worker) {
|
||||
let g = c; b || e._postWorkerMessageSync(g, "inversionMode", "both"); q = await new Promise((l, v) => {
|
||||
let w, u, r, y = -1; u = t => { t.data.id === y && (g.removeEventListener("message", u), g.removeEventListener("error", r), clearTimeout(w), null !== t.data.data ? l({ data: t.data.data, cornerPoints: e._convertPoints(t.data.cornerPoints, m) }) : v(e.NO_QR_CODE_FOUND)) }; r = t => {
|
||||
g.removeEventListener("message", u); g.removeEventListener("error", r); clearTimeout(w); v("Scanner error: " + (t ?
|
||||
t.message || t : "Unknown Error"))
|
||||
}; g.addEventListener("message", u); g.addEventListener("error", r); w = setTimeout(() => r("timeout"), 1E4); let x = k.getImageData(0, 0, d.width, d.height); y = e._postWorkerMessageSync(g, "decode", x, [x.data.buffer])
|
||||
})
|
||||
} else q = await Promise.race([new Promise((g, l) => window.setTimeout(() => l("Scanner error: timeout"), 1E4)), (async () => {
|
||||
try { var [g] = await c.detect(d); if (!g) throw e.NO_QR_CODE_FOUND; return { data: g.rawValue, cornerPoints: e._convertPoints(g.cornerPoints, m) } } catch (l) {
|
||||
g = l.message || l;
|
||||
if (/not implemented|service unavailable/.test(g)) return e._disableBarcodeDetector = !0, e.scanImage(a, { scanRegion: m, canvas: d, disallowCanvasResizing: f, alsoTryWithoutScanRegion: h }); throw `Scanner error: ${g}`;
|
||||
}
|
||||
})()]); return n ? q : q.data
|
||||
} catch (p) { if (!m || !h) throw p; let k = await e.scanImage(a, { qrEngine: c, canvas: d, disallowCanvasResizing: f }); return n ? k : k.data } finally { b || e._postWorkerMessage(c, "close") }
|
||||
} setGrayscaleWeights(a, b, c, d = !0) {
|
||||
e._postWorkerMessage(this._qrEnginePromise, "grayscaleWeights", {
|
||||
red: a, green: b,
|
||||
blue: c, useIntegerApproximation: d
|
||||
})
|
||||
} setInversionMode(a) { e._postWorkerMessage(this._qrEnginePromise, "inversionMode", a) } static async createQrEngine(a) {
|
||||
a && console.warn("Specifying a worker path is not required and not supported anymore."); a = () => import("./qr-scanner-worker.min.js").then(c => c.createWorker()); if (!(!e._disableBarcodeDetector && "BarcodeDetector" in window && BarcodeDetector.getSupportedFormats && (await BarcodeDetector.getSupportedFormats()).includes("qr_code"))) return a(); let b = navigator.userAgentData;
|
||||
return b && b.brands.some(({ brand: c }) => /Chromium/i.test(c)) && /mac ?OS/i.test(b.platform) && await b.getHighEntropyValues(["architecture", "platformVersion"]).then(({ architecture: c, platformVersion: d }) => /arm/i.test(c || "arm") && 13 <= parseInt(d || "13")).catch(() => !0) ? a() : new BarcodeDetector({ formats: ["qr_code"] })
|
||||
} _onPlay() { this._scanRegion = this._calculateScanRegion(this.$video); this._updateOverlay(); this.$overlay && (this.$overlay.style.display = ""); this._scanFrame() } _onLoadedMetaData() {
|
||||
this._scanRegion = this._calculateScanRegion(this.$video);
|
||||
this._updateOverlay()
|
||||
} _onVisibilityChange() { document.hidden ? this.pause() : this._active && this.start() } _calculateScanRegion(a) { let b = Math.round(2 / 3 * Math.min(a.videoWidth, a.videoHeight)); return { x: Math.round((a.videoWidth - b) / 2), y: Math.round((a.videoHeight - b) / 2), width: b, height: b, downScaledWidth: this._legacyCanvasSize, downScaledHeight: this._legacyCanvasSize } } _updateOverlay() {
|
||||
requestAnimationFrame(() => {
|
||||
if (this.$overlay) {
|
||||
var a = this.$video, b = a.videoWidth, c = a.videoHeight, d = a.offsetWidth, f = a.offsetHeight, h = a.offsetLeft,
|
||||
m = a.offsetTop, n = window.getComputedStyle(a), p = n.objectFit, k = b / c, q = d / f; switch (p) { case "none": var g = b; var l = c; break; case "fill": g = d; l = f; break; default: ("cover" === p ? k > q : k < q) ? (l = f, g = l * k) : (g = d, l = g / k), "scale-down" === p && (g = Math.min(g, b), l = Math.min(l, c)) }var [v, w] = n.objectPosition.split(" ").map((r, y) => { const x = parseFloat(r); return r.endsWith("%") ? (y ? f - l : d - g) * x / 100 : x }); n = this._scanRegion.width || b; q = this._scanRegion.height || c; p = this._scanRegion.x || 0; var u = this._scanRegion.y || 0; k = this.$overlay.style; k.width =
|
||||
`${n / b * g}px`; k.height = `${q / c * l}px`; k.top = `${m + w + u / c * l}px`; c = /scaleX\(-1\)/.test(a.style.transform); k.left = `${h + (c ? d - v - g : v) + (c ? b - p - n : p) / b * g}px`; k.transform = a.style.transform
|
||||
}
|
||||
})
|
||||
} static _convertPoints(a, b) { if (!b) return a; let c = b.x || 0, d = b.y || 0, f = b.width && b.downScaledWidth ? b.width / b.downScaledWidth : 1; b = b.height && b.downScaledHeight ? b.height / b.downScaledHeight : 1; for (let h of a) h.x = h.x * f + c, h.y = h.y * b + d; return a } _scanFrame() {
|
||||
!this._active || this.$video.paused || this.$video.ended || ("requestVideoFrameCallback" in
|
||||
this.$video ? this.$video.requestVideoFrameCallback.bind(this.$video) : requestAnimationFrame)(async () => {
|
||||
if (!(1 >= this.$video.readyState)) {
|
||||
var a = Date.now() - this._lastScanTimestamp, b = 1E3 / this._maxScansPerSecond; a < b && await new Promise(d => setTimeout(d, b - a)); this._lastScanTimestamp = Date.now(); try { var c = await e.scanImage(this.$video, { scanRegion: this._scanRegion, qrEngine: this._qrEnginePromise, canvas: this.$canvas }) } catch (d) { if (!this._active) return; this._onDecodeError(d) } !e._disableBarcodeDetector || await this._qrEnginePromise instanceof
|
||||
Worker || (this._qrEnginePromise = e.createQrEngine()); c ? (this._onDecode ? this._onDecode(c) : this._legacyOnDecode && this._legacyOnDecode(c.data), this.$codeOutlineHighlight && (clearTimeout(this._codeOutlineHighlightRemovalTimeout), this._codeOutlineHighlightRemovalTimeout = void 0, this.$codeOutlineHighlight.setAttribute("viewBox", `${this._scanRegion.x || 0} ` + `${this._scanRegion.y || 0} ` + `${this._scanRegion.width || this.$video.videoWidth} ` + `${this._scanRegion.height || this.$video.videoHeight}`), this.$codeOutlineHighlight.firstElementChild.setAttribute("points",
|
||||
c.cornerPoints.map(({ x: d, y: f }) => `${d},${f}`).join(" ")), this.$codeOutlineHighlight.style.display = "")) : this.$codeOutlineHighlight && !this._codeOutlineHighlightRemovalTimeout && (this._codeOutlineHighlightRemovalTimeout = setTimeout(() => this.$codeOutlineHighlight.style.display = "none", 100))
|
||||
} this._scanFrame()
|
||||
})
|
||||
} _onDecodeError(a) { a !== e.NO_QR_CODE_FOUND && console.log(a) } async _getCameraStream() {
|
||||
if (!navigator.mediaDevices) throw "Camera not found."; let a = /^(environment|user)$/.test(this._preferredCamera) ? "facingMode" :
|
||||
"deviceId", b = [{ width: { min: 1024 } }, { width: { min: 768 } }, {}], c = b.map(d => Object.assign({}, d, { [a]: { exact: this._preferredCamera } })); for (let d of [...c, ...b]) try { let f = await navigator.mediaDevices.getUserMedia({ video: d, audio: !1 }), h = this._getFacingMode(f) || (d.facingMode ? this._preferredCamera : "environment" === this._preferredCamera ? "user" : "environment"); return { stream: f, facingMode: h } } catch (f) { } throw "Camera not found.";
|
||||
} async _restartVideoStream() { let a = this._paused; await this.pause(!0) && !a && this._active && await this.start() } static _stopVideoStream(a) {
|
||||
for (let b of a.getTracks()) b.stop(),
|
||||
a.removeTrack(b)
|
||||
} _setVideoMirror(a) { this.$video.style.transform = "scaleX(" + ("user" === a ? -1 : 1) + ")" } _getFacingMode(a) { return (a = a.getVideoTracks()[0]) ? /rear|back|environment/i.test(a.label) ? "environment" : /front|user|face/i.test(a.label) ? "user" : null : null } static _drawToCanvas(a, b, c, d = !1) {
|
||||
c = c || document.createElement("canvas"); let f = b && b.x ? b.x : 0, h = b && b.y ? b.y : 0, m = b && b.width ? b.width : a.videoWidth || a.width, n = b && b.height ? b.height : a.videoHeight || a.height; d || (d = b && b.downScaledWidth ? b.downScaledWidth : m, b = b && b.downScaledHeight ?
|
||||
b.downScaledHeight : n, c.width !== d && (c.width = d), c.height !== b && (c.height = b)); b = c.getContext("2d", { alpha: !1 }); b.imageSmoothingEnabled = !1; b.drawImage(a, f, h, m, n, 0, 0, c.width, c.height); return [c, b]
|
||||
} static async _loadImage(a) {
|
||||
if (a instanceof Image) return await e._awaitImageLoad(a), a; if (a instanceof HTMLVideoElement || a instanceof HTMLCanvasElement || a instanceof SVGImageElement || "OffscreenCanvas" in window && a instanceof OffscreenCanvas || "ImageBitmap" in window && a instanceof ImageBitmap) return a; if (a instanceof
|
||||
File || a instanceof Blob || a instanceof URL || "string" === typeof a) { let b = new Image; b.src = a instanceof File || a instanceof Blob ? URL.createObjectURL(a) : a.toString(); try { return await e._awaitImageLoad(b), b } finally { (a instanceof File || a instanceof Blob) && URL.revokeObjectURL(b.src) } } else throw "Unsupported image type.";
|
||||
} static async _awaitImageLoad(a) {
|
||||
a.complete && 0 !== a.naturalWidth || await new Promise((b, c) => {
|
||||
let d = f => {
|
||||
a.removeEventListener("load", d); a.removeEventListener("error", d); f instanceof ErrorEvent ?
|
||||
c("Image load error") : b()
|
||||
}; a.addEventListener("load", d); a.addEventListener("error", d)
|
||||
})
|
||||
} static async _postWorkerMessage(a, b, c, d) { return e._postWorkerMessageSync(await a, b, c, d) } static _postWorkerMessageSync(a, b, c, d) { if (!(a instanceof Worker)) return -1; let f = e._workerMessageId++; a.postMessage({ id: f, type: b, data: c }, d); return f }
|
||||
} e.DEFAULT_CANVAS_SIZE = 400; e.NO_QR_CODE_FOUND = "No QR code found"; e._disableBarcodeDetector = !1; e._workerMessageId = 0; export default e
|
||||
constructor(a, b, c, d, f) {
|
||||
this._legacyCanvasSize = e.DEFAULT_CANVAS_SIZE;
|
||||
this._preferredCamera = "environment";
|
||||
this._maxScansPerSecond = 25;
|
||||
this._lastScanTimestamp = -1;
|
||||
this._destroyed = this._flashOn = this._paused = this._active = !1;
|
||||
this.$video = a;
|
||||
this.$canvas = document.createElement("canvas");
|
||||
c && "object" === typeof c
|
||||
? (this._onDecode = b)
|
||||
: (c || d || f
|
||||
? console.warn(
|
||||
"You're using a deprecated version of the QrScanner constructor which will be removed in the future"
|
||||
)
|
||||
: console.warn(
|
||||
"Note that the type of the scan result passed to onDecode will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true."
|
||||
),
|
||||
(this._legacyOnDecode = b));
|
||||
b = "object" === typeof c ? c : {};
|
||||
this._onDecodeError =
|
||||
b.onDecodeError || ("function" === typeof c ? c : this._onDecodeError);
|
||||
this._calculateScanRegion =
|
||||
b.calculateScanRegion ||
|
||||
("function" === typeof d ? d : this._calculateScanRegion);
|
||||
this._preferredCamera = b.preferredCamera || f || this._preferredCamera;
|
||||
this._legacyCanvasSize =
|
||||
"number" === typeof c
|
||||
? c
|
||||
: "number" === typeof d
|
||||
? d
|
||||
: this._legacyCanvasSize;
|
||||
this._maxScansPerSecond = b.maxScansPerSecond || this._maxScansPerSecond;
|
||||
this._onPlay = this._onPlay.bind(this);
|
||||
this._onLoadedMetaData = this._onLoadedMetaData.bind(this);
|
||||
this._onVisibilityChange = this._onVisibilityChange.bind(this);
|
||||
this._updateOverlay = this._updateOverlay.bind(this);
|
||||
a.disablePictureInPicture = !0;
|
||||
a.playsInline = !0;
|
||||
a.muted = !0;
|
||||
let h = !1;
|
||||
a.hidden && ((a.hidden = !1), (h = !0));
|
||||
document.body.contains(a) || (document.body.appendChild(a), (h = !0));
|
||||
c = a.parentElement;
|
||||
if (b.highlightScanRegion || b.highlightCodeOutline) {
|
||||
d = !!b.overlay;
|
||||
this.$overlay = b.overlay || document.createElement("div");
|
||||
f = this.$overlay.style;
|
||||
f.position = "absolute";
|
||||
f.display = "none";
|
||||
f.pointerEvents = "none";
|
||||
this.$overlay.classList.add("scan-region-highlight");
|
||||
if (!d && b.highlightScanRegion) {
|
||||
this.$overlay.innerHTML =
|
||||
'<svg class="scan-region-highlight-svg" viewBox="0 0 238 238" preserveAspectRatio="none" style="position:absolute;width:100%;height:100%;left:0;top:0;fill:none;stroke:#e9b213;stroke-width:4;stroke-linecap:round;stroke-linejoin:round"><path d="M31 2H10a8 8 0 0 0-8 8v21M207 2h21a8 8 0 0 1 8 8v21m0 176v21a8 8 0 0 1-8 8h-21m-176 0H10a8 8 0 0 1-8-8v-21"/></svg>';
|
||||
try {
|
||||
this.$overlay.firstElementChild.animate(
|
||||
{
|
||||
transform: ["scale(.98)", "scale(1.01)"],
|
||||
},
|
||||
{
|
||||
duration: 400,
|
||||
iterations: Infinity,
|
||||
direction: "alternate",
|
||||
easing: "ease-in-out",
|
||||
}
|
||||
);
|
||||
} catch (m) {}
|
||||
c.insertBefore(this.$overlay, this.$video.nextSibling);
|
||||
}
|
||||
b.highlightCodeOutline &&
|
||||
(this.$overlay.insertAdjacentHTML(
|
||||
"beforeend",
|
||||
'<svg class="code-outline-highlight" preserveAspectRatio="none" style="display:none;width:100%;height:100%;fill:none;stroke:#e9b213;stroke-width:5;stroke-dasharray:25;stroke-linecap:round;stroke-linejoin:round"><polygon/></svg>'
|
||||
),
|
||||
(this.$codeOutlineHighlight = this.$overlay.lastElementChild));
|
||||
}
|
||||
this._scanRegion = this._calculateScanRegion(a);
|
||||
requestAnimationFrame(() => {
|
||||
let m = window.getComputedStyle(a);
|
||||
"none" === m.display &&
|
||||
(a.style.setProperty("display", "block", "important"), (h = !0));
|
||||
"visible" !== m.visibility &&
|
||||
(a.style.setProperty("visibility", "visible", "important"), (h = !0));
|
||||
h &&
|
||||
(console.warn(
|
||||
"QrScanner has overwritten the video hiding style to avoid Safari stopping the playback."
|
||||
),
|
||||
(a.style.opacity = "0"),
|
||||
(a.style.width = "0"),
|
||||
(a.style.height = "0"),
|
||||
this.$overlay &&
|
||||
this.$overlay.parentElement &&
|
||||
this.$overlay.parentElement.removeChild(this.$overlay),
|
||||
delete this.$overlay,
|
||||
delete this.$codeOutlineHighlight);
|
||||
this.$overlay && this._updateOverlay();
|
||||
});
|
||||
a.addEventListener("play", this._onPlay);
|
||||
a.addEventListener("loadedmetadata", this._onLoadedMetaData);
|
||||
document.addEventListener("visibilitychange", this._onVisibilityChange);
|
||||
window.addEventListener("resize", this._updateOverlay);
|
||||
this._qrEnginePromise = e.createQrEngine();
|
||||
}
|
||||
static set WORKER_PATH(a) {
|
||||
console.warn(
|
||||
"Setting QrScanner.WORKER_PATH is not required and not supported anymore. Have a look at the README for new setup instructions."
|
||||
);
|
||||
}
|
||||
static async hasCamera() {
|
||||
try {
|
||||
return !!(await e.listCameras(!1)).length;
|
||||
} catch (a) {
|
||||
return !1;
|
||||
}
|
||||
}
|
||||
static async listCameras(a = !1) {
|
||||
if (!navigator.mediaDevices) return [];
|
||||
let b = async () =>
|
||||
(await navigator.mediaDevices.enumerateDevices()).filter(
|
||||
(d) => "videoinput" === d.kind
|
||||
),
|
||||
c;
|
||||
try {
|
||||
a &&
|
||||
(await b()).every((d) => !d.label) &&
|
||||
(c = await navigator.mediaDevices.getUserMedia({
|
||||
audio: !1,
|
||||
video: !0,
|
||||
}));
|
||||
} catch (d) {}
|
||||
try {
|
||||
return (await b()).map((d, f) => ({
|
||||
id: d.deviceId,
|
||||
label: d.label || (0 === f ? "Default Camera" : `Camera ${f + 1}`),
|
||||
}));
|
||||
} finally {
|
||||
c &&
|
||||
(console.warn(
|
||||
"Call listCameras after successfully starting a QR scanner to avoid creating a temporary video stream"
|
||||
),
|
||||
e._stopVideoStream(c));
|
||||
}
|
||||
}
|
||||
async hasFlash() {
|
||||
let a;
|
||||
try {
|
||||
if (this.$video.srcObject) {
|
||||
if (!(this.$video.srcObject instanceof MediaStream)) return !1;
|
||||
a = this.$video.srcObject;
|
||||
} else a = (await this._getCameraStream()).stream;
|
||||
return "torch" in a.getVideoTracks()[0].getSettings();
|
||||
} catch (b) {
|
||||
return !1;
|
||||
} finally {
|
||||
a &&
|
||||
a !== this.$video.srcObject &&
|
||||
(console.warn(
|
||||
"Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream"
|
||||
),
|
||||
e._stopVideoStream(a));
|
||||
}
|
||||
}
|
||||
isFlashOn() {
|
||||
return this._flashOn;
|
||||
}
|
||||
async toggleFlash() {
|
||||
this._flashOn ? await this.turnFlashOff() : await this.turnFlashOn();
|
||||
}
|
||||
async turnFlashOn() {
|
||||
if (
|
||||
!this._flashOn &&
|
||||
!this._destroyed &&
|
||||
((this._flashOn = !0), this._active && !this._paused)
|
||||
)
|
||||
try {
|
||||
if (!(await this.hasFlash())) throw "No flash available";
|
||||
await this.$video.srcObject
|
||||
.getVideoTracks()[0]
|
||||
.applyConstraints({ advanced: [{ torch: !0 }] });
|
||||
} catch (a) {
|
||||
throw ((this._flashOn = !1), a);
|
||||
}
|
||||
}
|
||||
async turnFlashOff() {
|
||||
this._flashOn && ((this._flashOn = !1), await this._restartVideoStream());
|
||||
}
|
||||
destroy() {
|
||||
this.$video.removeEventListener("loadedmetadata", this._onLoadedMetaData);
|
||||
this.$video.removeEventListener("play", this._onPlay);
|
||||
document.removeEventListener("visibilitychange", this._onVisibilityChange);
|
||||
window.removeEventListener("resize", this._updateOverlay);
|
||||
this._destroyed = !0;
|
||||
this._flashOn = !1;
|
||||
this.stop();
|
||||
e._postWorkerMessage(this._qrEnginePromise, "close");
|
||||
}
|
||||
async start() {
|
||||
if (this._destroyed)
|
||||
throw Error(
|
||||
"The QR scanner can not be started as it had been destroyed."
|
||||
);
|
||||
if (!this._active || this._paused)
|
||||
if (
|
||||
("https:" !== window.location.protocol &&
|
||||
console.warn(
|
||||
"The camera stream is only accessible if the page is transferred via https."
|
||||
),
|
||||
(this._active = !0),
|
||||
!document.hidden)
|
||||
)
|
||||
if (((this._paused = !1), this.$video.srcObject))
|
||||
await this.$video.play();
|
||||
else
|
||||
try {
|
||||
let { stream: a, facingMode: b } = await this._getCameraStream();
|
||||
!this._active || this._paused
|
||||
? e._stopVideoStream(a)
|
||||
: (this._setVideoMirror(b),
|
||||
(this.$video.srcObject = a),
|
||||
await this.$video.play(),
|
||||
this._flashOn &&
|
||||
((this._flashOn = !1), this.turnFlashOn().catch(() => {})));
|
||||
} catch (a) {
|
||||
if (!this._paused) throw ((this._active = !1), a);
|
||||
}
|
||||
}
|
||||
stop() {
|
||||
this.pause();
|
||||
this._active = !1;
|
||||
}
|
||||
async pause(a = !1) {
|
||||
this._paused = !0;
|
||||
if (!this._active) return !0;
|
||||
this.$video.pause();
|
||||
this.$overlay && (this.$overlay.style.display = "none");
|
||||
let b = () => {
|
||||
this.$video.srcObject instanceof MediaStream &&
|
||||
(e._stopVideoStream(this.$video.srcObject),
|
||||
(this.$video.srcObject = null));
|
||||
};
|
||||
if (a) return b(), !0;
|
||||
await new Promise((c) => setTimeout(c, 300));
|
||||
if (!this._paused) return !1;
|
||||
b();
|
||||
return !0;
|
||||
}
|
||||
async setCamera(a) {
|
||||
a !== this._preferredCamera &&
|
||||
((this._preferredCamera = a), await this._restartVideoStream());
|
||||
}
|
||||
static async scanImage(a, b, c, d, f = !1, h = !1) {
|
||||
let m,
|
||||
n = !1;
|
||||
b &&
|
||||
("scanRegion" in b ||
|
||||
"qrEngine" in b ||
|
||||
"canvas" in b ||
|
||||
"disallowCanvasResizing" in b ||
|
||||
"alsoTryWithoutScanRegion" in b ||
|
||||
"returnDetailedScanResult" in b)
|
||||
? ((m = b.scanRegion),
|
||||
(c = b.qrEngine),
|
||||
(d = b.canvas),
|
||||
(f = b.disallowCanvasResizing || !1),
|
||||
(h = b.alsoTryWithoutScanRegion || !1),
|
||||
(n = !0))
|
||||
: b || c || d || f || h
|
||||
? console.warn(
|
||||
"You're using a deprecated api for scanImage which will be removed in the future."
|
||||
)
|
||||
: console.warn(
|
||||
"Note that the return type of scanImage will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true."
|
||||
);
|
||||
b = !!c;
|
||||
try {
|
||||
let p, k;
|
||||
[c, p] = await Promise.all([c || e.createQrEngine(), e._loadImage(a)]);
|
||||
[d, k] = e._drawToCanvas(p, m, d, f);
|
||||
let q;
|
||||
if (c instanceof Worker) {
|
||||
let g = c;
|
||||
b || e._postWorkerMessageSync(g, "inversionMode", "both");
|
||||
q = await new Promise((l, v) => {
|
||||
let w,
|
||||
u,
|
||||
r,
|
||||
y = -1;
|
||||
u = (t) => {
|
||||
t.data.id === y &&
|
||||
(g.removeEventListener("message", u),
|
||||
g.removeEventListener("error", r),
|
||||
clearTimeout(w),
|
||||
null !== t.data.data
|
||||
? l({
|
||||
data: t.data.data,
|
||||
cornerPoints: e._convertPoints(t.data.cornerPoints, m),
|
||||
})
|
||||
: v(e.NO_QR_CODE_FOUND));
|
||||
};
|
||||
r = (t) => {
|
||||
g.removeEventListener("message", u);
|
||||
g.removeEventListener("error", r);
|
||||
clearTimeout(w);
|
||||
v("Scanner error: " + (t ? t.message || t : "Unknown Error"));
|
||||
};
|
||||
g.addEventListener("message", u);
|
||||
g.addEventListener("error", r);
|
||||
w = setTimeout(() => r("timeout"), 1e4);
|
||||
let x = k.getImageData(0, 0, d.width, d.height);
|
||||
y = e._postWorkerMessageSync(g, "decode", x, [x.data.buffer]);
|
||||
});
|
||||
} else
|
||||
q = await Promise.race([
|
||||
new Promise((g, l) =>
|
||||
window.setTimeout(() => l("Scanner error: timeout"), 1e4)
|
||||
),
|
||||
(async () => {
|
||||
try {
|
||||
var [g] = await c.detect(d);
|
||||
if (!g) throw e.NO_QR_CODE_FOUND;
|
||||
return {
|
||||
data: g.rawValue,
|
||||
cornerPoints: e._convertPoints(g.cornerPoints, m),
|
||||
};
|
||||
} catch (l) {
|
||||
g = l.message || l;
|
||||
if (/not implemented|service unavailable/.test(g))
|
||||
return (
|
||||
(e._disableBarcodeDetector = !0),
|
||||
e.scanImage(a, {
|
||||
scanRegion: m,
|
||||
canvas: d,
|
||||
disallowCanvasResizing: f,
|
||||
alsoTryWithoutScanRegion: h,
|
||||
})
|
||||
);
|
||||
throw `Scanner error: ${g}`;
|
||||
}
|
||||
})(),
|
||||
]);
|
||||
return n ? q : q.data;
|
||||
} catch (p) {
|
||||
if (!m || !h) throw p;
|
||||
let k = await e.scanImage(a, {
|
||||
qrEngine: c,
|
||||
canvas: d,
|
||||
disallowCanvasResizing: f,
|
||||
});
|
||||
return n ? k : k.data;
|
||||
} finally {
|
||||
b || e._postWorkerMessage(c, "close");
|
||||
}
|
||||
}
|
||||
setGrayscaleWeights(a, b, c, d = !0) {
|
||||
e._postWorkerMessage(this._qrEnginePromise, "grayscaleWeights", {
|
||||
red: a,
|
||||
green: b,
|
||||
blue: c,
|
||||
useIntegerApproximation: d,
|
||||
});
|
||||
}
|
||||
setInversionMode(a) {
|
||||
e._postWorkerMessage(this._qrEnginePromise, "inversionMode", a);
|
||||
}
|
||||
static async createQrEngine(a) {
|
||||
a &&
|
||||
console.warn(
|
||||
"Specifying a worker path is not required and not supported anymore."
|
||||
);
|
||||
a = () =>
|
||||
import("./qr-scanner-worker.min.js").then((c) => c.createWorker());
|
||||
if (
|
||||
!(
|
||||
!e._disableBarcodeDetector &&
|
||||
"BarcodeDetector" in window &&
|
||||
BarcodeDetector.getSupportedFormats &&
|
||||
(await BarcodeDetector.getSupportedFormats()).includes("qr_code")
|
||||
)
|
||||
)
|
||||
return a();
|
||||
let b = navigator.userAgentData;
|
||||
return b &&
|
||||
b.brands.some(({ brand: c }) => /Chromium/i.test(c)) &&
|
||||
/mac ?OS/i.test(b.platform) &&
|
||||
(await b
|
||||
.getHighEntropyValues(["architecture", "platformVersion"])
|
||||
.then(
|
||||
({ architecture: c, platformVersion: d }) =>
|
||||
/arm/i.test(c || "arm") && 13 <= parseInt(d || "13")
|
||||
)
|
||||
.catch(() => !0))
|
||||
? a()
|
||||
: new BarcodeDetector({ formats: ["qr_code"] });
|
||||
}
|
||||
_onPlay() {
|
||||
this._scanRegion = this._calculateScanRegion(this.$video);
|
||||
this._updateOverlay();
|
||||
this.$overlay && (this.$overlay.style.display = "");
|
||||
this._scanFrame();
|
||||
}
|
||||
_onLoadedMetaData() {
|
||||
this._scanRegion = this._calculateScanRegion(this.$video);
|
||||
this._updateOverlay();
|
||||
}
|
||||
_onVisibilityChange() {
|
||||
document.hidden ? this.pause() : this._active && this.start();
|
||||
}
|
||||
_calculateScanRegion(a) {
|
||||
let b = Math.round((2 / 3) * Math.min(a.videoWidth, a.videoHeight));
|
||||
return {
|
||||
x: Math.round((a.videoWidth - b) / 2),
|
||||
y: Math.round((a.videoHeight - b) / 2),
|
||||
width: b,
|
||||
height: b,
|
||||
downScaledWidth: this._legacyCanvasSize,
|
||||
downScaledHeight: this._legacyCanvasSize,
|
||||
};
|
||||
}
|
||||
_updateOverlay() {
|
||||
requestAnimationFrame(() => {
|
||||
if (this.$overlay) {
|
||||
var a = this.$video,
|
||||
b = a.videoWidth,
|
||||
c = a.videoHeight,
|
||||
d = a.offsetWidth,
|
||||
f = a.offsetHeight,
|
||||
h = a.offsetLeft,
|
||||
m = a.offsetTop,
|
||||
n = window.getComputedStyle(a),
|
||||
p = n.objectFit,
|
||||
k = b / c,
|
||||
q = d / f;
|
||||
switch (p) {
|
||||
case "none":
|
||||
var g = b;
|
||||
var l = c;
|
||||
break;
|
||||
case "fill":
|
||||
g = d;
|
||||
l = f;
|
||||
break;
|
||||
default:
|
||||
("cover" === p ? k > q : k < q)
|
||||
? ((l = f), (g = l * k))
|
||||
: ((g = d), (l = g / k)),
|
||||
"scale-down" === p &&
|
||||
((g = Math.min(g, b)), (l = Math.min(l, c)));
|
||||
}
|
||||
var [v, w] = n.objectPosition.split(" ").map((r, y) => {
|
||||
const x = parseFloat(r);
|
||||
return r.endsWith("%") ? ((y ? f - l : d - g) * x) / 100 : x;
|
||||
});
|
||||
n = this._scanRegion.width || b;
|
||||
q = this._scanRegion.height || c;
|
||||
p = this._scanRegion.x || 0;
|
||||
var u = this._scanRegion.y || 0;
|
||||
k = this.$overlay.style;
|
||||
k.width = `${(n / b) * g}px`;
|
||||
k.height = `${(q / c) * l}px`;
|
||||
k.top = `${m + w + (u / c) * l}px`;
|
||||
c = /scaleX\(-1\)/.test(a.style.transform);
|
||||
k.left = `${h + (c ? d - v - g : v) + ((c ? b - p - n : p) / b) * g}px`;
|
||||
k.transform = a.style.transform;
|
||||
}
|
||||
});
|
||||
}
|
||||
static _convertPoints(a, b) {
|
||||
if (!b) return a;
|
||||
let c = b.x || 0,
|
||||
d = b.y || 0,
|
||||
f = b.width && b.downScaledWidth ? b.width / b.downScaledWidth : 1;
|
||||
b = b.height && b.downScaledHeight ? b.height / b.downScaledHeight : 1;
|
||||
for (let h of a) (h.x = h.x * f + c), (h.y = h.y * b + d);
|
||||
return a;
|
||||
}
|
||||
_scanFrame() {
|
||||
!this._active ||
|
||||
this.$video.paused ||
|
||||
this.$video.ended ||
|
||||
("requestVideoFrameCallback" in this.$video
|
||||
? this.$video.requestVideoFrameCallback.bind(this.$video)
|
||||
: requestAnimationFrame)(async () => {
|
||||
if (!(1 >= this.$video.readyState)) {
|
||||
var a = Date.now() - this._lastScanTimestamp,
|
||||
b = 1e3 / this._maxScansPerSecond;
|
||||
a < b && (await new Promise((d) => setTimeout(d, b - a)));
|
||||
this._lastScanTimestamp = Date.now();
|
||||
try {
|
||||
var c = await e.scanImage(this.$video, {
|
||||
scanRegion: this._scanRegion,
|
||||
qrEngine: this._qrEnginePromise,
|
||||
canvas: this.$canvas,
|
||||
});
|
||||
} catch (d) {
|
||||
if (!this._active) return;
|
||||
this._onDecodeError(d);
|
||||
}
|
||||
!e._disableBarcodeDetector ||
|
||||
(await this._qrEnginePromise) instanceof Worker ||
|
||||
(this._qrEnginePromise = e.createQrEngine());
|
||||
c
|
||||
? (this._onDecode
|
||||
? this._onDecode(c)
|
||||
: this._legacyOnDecode && this._legacyOnDecode(c.data),
|
||||
this.$codeOutlineHighlight &&
|
||||
(clearTimeout(this._codeOutlineHighlightRemovalTimeout),
|
||||
(this._codeOutlineHighlightRemovalTimeout = void 0),
|
||||
this.$codeOutlineHighlight.setAttribute(
|
||||
"viewBox",
|
||||
`${this._scanRegion.x || 0} ` +
|
||||
`${this._scanRegion.y || 0} ` +
|
||||
`${this._scanRegion.width || this.$video.videoWidth} ` +
|
||||
`${this._scanRegion.height || this.$video.videoHeight}`
|
||||
),
|
||||
this.$codeOutlineHighlight.firstElementChild.setAttribute(
|
||||
"points",
|
||||
c.cornerPoints.map(({ x: d, y: f }) => `${d},${f}`).join(" ")
|
||||
),
|
||||
(this.$codeOutlineHighlight.style.display = "")))
|
||||
: this.$codeOutlineHighlight &&
|
||||
!this._codeOutlineHighlightRemovalTimeout &&
|
||||
(this._codeOutlineHighlightRemovalTimeout = setTimeout(
|
||||
() => (this.$codeOutlineHighlight.style.display = "none"),
|
||||
100
|
||||
));
|
||||
}
|
||||
this._scanFrame();
|
||||
});
|
||||
}
|
||||
_onDecodeError(a) {
|
||||
a !== e.NO_QR_CODE_FOUND && console.log(a);
|
||||
}
|
||||
async _getCameraStream() {
|
||||
if (!navigator.mediaDevices) throw "Camera not found.";
|
||||
let a = /^(environment|user)$/.test(this._preferredCamera)
|
||||
? "facingMode"
|
||||
: "deviceId",
|
||||
b = [{ width: { min: 1024 } }, { width: { min: 768 } }, {}],
|
||||
c = b.map((d) =>
|
||||
Object.assign({}, d, { [a]: { exact: this._preferredCamera } })
|
||||
);
|
||||
for (let d of [...c, ...b])
|
||||
try {
|
||||
let f = await navigator.mediaDevices.getUserMedia({
|
||||
video: d,
|
||||
audio: !1,
|
||||
}),
|
||||
h =
|
||||
this._getFacingMode(f) ||
|
||||
(d.facingMode
|
||||
? this._preferredCamera
|
||||
: "environment" === this._preferredCamera
|
||||
? "user"
|
||||
: "environment");
|
||||
return { stream: f, facingMode: h };
|
||||
} catch (f) {}
|
||||
throw "Camera not found.";
|
||||
}
|
||||
async _restartVideoStream() {
|
||||
let a = this._paused;
|
||||
(await this.pause(!0)) && !a && this._active && (await this.start());
|
||||
}
|
||||
static _stopVideoStream(a) {
|
||||
for (let b of a.getTracks()) b.stop(), a.removeTrack(b);
|
||||
}
|
||||
_setVideoMirror(a) {
|
||||
this.$video.style.transform = "scaleX(" + ("user" === a ? -1 : 1) + ")";
|
||||
}
|
||||
_getFacingMode(a) {
|
||||
return (a = a.getVideoTracks()[0])
|
||||
? /rear|back|environment/i.test(a.label)
|
||||
? "environment"
|
||||
: /front|user|face/i.test(a.label)
|
||||
? "user"
|
||||
: null
|
||||
: null;
|
||||
}
|
||||
static _drawToCanvas(a, b, c, d = !1) {
|
||||
c = c || document.createElement("canvas");
|
||||
let f = b && b.x ? b.x : 0,
|
||||
h = b && b.y ? b.y : 0,
|
||||
m = b && b.width ? b.width : a.videoWidth || a.width,
|
||||
n = b && b.height ? b.height : a.videoHeight || a.height;
|
||||
d ||
|
||||
((d = b && b.downScaledWidth ? b.downScaledWidth : m),
|
||||
(b = b && b.downScaledHeight ? b.downScaledHeight : n),
|
||||
c.width !== d && (c.width = d),
|
||||
c.height !== b && (c.height = b));
|
||||
b = c.getContext("2d", { alpha: !1 });
|
||||
b.imageSmoothingEnabled = !1;
|
||||
b.drawImage(a, f, h, m, n, 0, 0, c.width, c.height);
|
||||
return [c, b];
|
||||
}
|
||||
static async _loadImage(a) {
|
||||
if (a instanceof Image) return await e._awaitImageLoad(a), a;
|
||||
if (
|
||||
a instanceof HTMLVideoElement ||
|
||||
a instanceof HTMLCanvasElement ||
|
||||
a instanceof SVGImageElement ||
|
||||
("OffscreenCanvas" in window && a instanceof OffscreenCanvas) ||
|
||||
("ImageBitmap" in window && a instanceof ImageBitmap)
|
||||
)
|
||||
return a;
|
||||
if (
|
||||
a instanceof File ||
|
||||
a instanceof Blob ||
|
||||
a instanceof URL ||
|
||||
"string" === typeof a
|
||||
) {
|
||||
let b = new Image();
|
||||
b.src =
|
||||
a instanceof File || a instanceof Blob
|
||||
? URL.createObjectURL(a)
|
||||
: a.toString();
|
||||
try {
|
||||
return await e._awaitImageLoad(b), b;
|
||||
} finally {
|
||||
(a instanceof File || a instanceof Blob) && URL.revokeObjectURL(b.src);
|
||||
}
|
||||
} else throw "Unsupported image type.";
|
||||
}
|
||||
static async _awaitImageLoad(a) {
|
||||
(a.complete && 0 !== a.naturalWidth) ||
|
||||
(await new Promise((b, c) => {
|
||||
let d = (f) => {
|
||||
a.removeEventListener("load", d);
|
||||
a.removeEventListener("error", d);
|
||||
f instanceof ErrorEvent ? c("Image load error") : b();
|
||||
};
|
||||
a.addEventListener("load", d);
|
||||
a.addEventListener("error", d);
|
||||
}));
|
||||
}
|
||||
static async _postWorkerMessage(a, b, c, d) {
|
||||
return e._postWorkerMessageSync(await a, b, c, d);
|
||||
}
|
||||
static _postWorkerMessageSync(a, b, c, d) {
|
||||
if (!(a instanceof Worker)) return -1;
|
||||
let f = e._workerMessageId++;
|
||||
a.postMessage({ id: f, type: b, data: c }, d);
|
||||
return f;
|
||||
}
|
||||
}
|
||||
e.DEFAULT_CANVAS_SIZE = 400;
|
||||
e.NO_QR_CODE_FOUND = "No QR code found";
|
||||
e._disableBarcodeDetector = !1;
|
||||
e._workerMessageId = 0;
|
||||
export default e;
|
||||
//# sourceMappingURL=qr-scanner.min.js.map
|
||||
|
763
require/js/qr-scanner/qr-scanner.umd.min.js
vendored
763
require/js/qr-scanner/qr-scanner.umd.min.js
vendored
@ -1,101 +1,666 @@
|
||||
'use strict'; (function (e, a) { "object" === typeof exports && "undefined" !== typeof module ? module.exports = a() : "function" === typeof define && define.amd ? define(a) : (e = "undefined" !== typeof globalThis ? globalThis : e || self, e.QrScanner = a()) })(this, function () {
|
||||
class e {
|
||||
constructor(a, b, c, d, f) {
|
||||
this._legacyCanvasSize = e.DEFAULT_CANVAS_SIZE; this._preferredCamera = "environment"; this._maxScansPerSecond = 25; this._lastScanTimestamp = -1; this._destroyed = this._flashOn = this._paused = this._active = !1; this.$video = a; this.$canvas = document.createElement("canvas");
|
||||
c && "object" === typeof c ? this._onDecode = b : (c || d || f ? console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future") : console.warn("Note that the type of the scan result passed to onDecode will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true."), this._legacyOnDecode = b); b = "object" === typeof c ? c : {}; this._onDecodeError = b.onDecodeError || ("function" === typeof c ? c : this._onDecodeError); this._calculateScanRegion =
|
||||
b.calculateScanRegion || ("function" === typeof d ? d : this._calculateScanRegion); this._preferredCamera = b.preferredCamera || f || this._preferredCamera; this._legacyCanvasSize = "number" === typeof c ? c : "number" === typeof d ? d : this._legacyCanvasSize; this._maxScansPerSecond = b.maxScansPerSecond || this._maxScansPerSecond; this._onPlay = this._onPlay.bind(this); this._onLoadedMetaData = this._onLoadedMetaData.bind(this); this._onVisibilityChange = this._onVisibilityChange.bind(this); this._updateOverlay = this._updateOverlay.bind(this);
|
||||
a.disablePictureInPicture = !0; a.playsInline = !0; a.muted = !0; let h = !1; a.hidden && (a.hidden = !1, h = !0); document.body.contains(a) || (document.body.appendChild(a), h = !0); c = a.parentElement; if (b.highlightScanRegion || b.highlightCodeOutline) {
|
||||
d = !!b.overlay; this.$overlay = b.overlay || document.createElement("div"); f = this.$overlay.style; f.position = "absolute"; f.display = "none"; f.pointerEvents = "none"; this.$overlay.classList.add("scan-region-highlight"); if (!d && b.highlightScanRegion) {
|
||||
this.$overlay.innerHTML = '<svg class="scan-region-highlight-svg" viewBox="0 0 238 238" preserveAspectRatio="none" style="position:absolute;width:100%;height:100%;left:0;top:0;fill:none;stroke:#e9b213;stroke-width:4;stroke-linecap:round;stroke-linejoin:round"><path d="M31 2H10a8 8 0 0 0-8 8v21M207 2h21a8 8 0 0 1 8 8v21m0 176v21a8 8 0 0 1-8 8h-21m-176 0H10a8 8 0 0 1-8-8v-21"/></svg>';
|
||||
try { this.$overlay.firstElementChild.animate({ transform: ["scale(.98)", "scale(1.01)"] }, { duration: 400, iterations: Infinity, direction: "alternate", easing: "ease-in-out" }) } catch (m) { } c.insertBefore(this.$overlay, this.$video.nextSibling)
|
||||
} b.highlightCodeOutline && (this.$overlay.insertAdjacentHTML("beforeend", '<svg class="code-outline-highlight" preserveAspectRatio="none" style="display:none;width:100%;height:100%;fill:none;stroke:#e9b213;stroke-width:5;stroke-dasharray:25;stroke-linecap:round;stroke-linejoin:round"><polygon/></svg>'),
|
||||
this.$codeOutlineHighlight = this.$overlay.lastElementChild)
|
||||
} this._scanRegion = this._calculateScanRegion(a); requestAnimationFrame(() => {
|
||||
let m = window.getComputedStyle(a); "none" === m.display && (a.style.setProperty("display", "block", "important"), h = !0); "visible" !== m.visibility && (a.style.setProperty("visibility", "visible", "important"), h = !0); h && (console.warn("QrScanner has overwritten the video hiding style to avoid Safari stopping the playback."), a.style.opacity = "0", a.style.width = "0", a.style.height = "0", this.$overlay &&
|
||||
this.$overlay.parentElement && this.$overlay.parentElement.removeChild(this.$overlay), delete this.$overlay, delete this.$codeOutlineHighlight); this.$overlay && this._updateOverlay()
|
||||
}); a.addEventListener("play", this._onPlay); a.addEventListener("loadedmetadata", this._onLoadedMetaData); document.addEventListener("visibilitychange", this._onVisibilityChange); window.addEventListener("resize", this._updateOverlay); this._qrEnginePromise = e.createQrEngine()
|
||||
} static set WORKER_PATH(a) { console.warn("Setting QrScanner.WORKER_PATH is not required and not supported anymore. Have a look at the README for new setup instructions.") } static async hasCamera() { try { return !!(await e.listCameras(!1)).length } catch (a) { return !1 } } static async listCameras(a =
|
||||
!1) { if (!navigator.mediaDevices) return []; let b = async () => (await navigator.mediaDevices.enumerateDevices()).filter(d => "videoinput" === d.kind), c; try { a && (await b()).every(d => !d.label) && (c = await navigator.mediaDevices.getUserMedia({ audio: !1, video: !0 })) } catch (d) { } try { return (await b()).map((d, f) => ({ id: d.deviceId, label: d.label || (0 === f ? "Default Camera" : `Camera ${f + 1}`) })) } finally { c && (console.warn("Call listCameras after successfully starting a QR scanner to avoid creating a temporary video stream"), e._stopVideoStream(c)) } } async hasFlash() {
|
||||
let a;
|
||||
try { if (this.$video.srcObject) { if (!(this.$video.srcObject instanceof MediaStream)) return !1; a = this.$video.srcObject } else a = (await this._getCameraStream()).stream; return "torch" in a.getVideoTracks()[0].getSettings() } catch (b) { return !1 } finally { a && a !== this.$video.srcObject && (console.warn("Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream"), e._stopVideoStream(a)) }
|
||||
} isFlashOn() { return this._flashOn } async toggleFlash() { this._flashOn ? await this.turnFlashOff() : await this.turnFlashOn() } async turnFlashOn() {
|
||||
if (!this._flashOn &&
|
||||
!this._destroyed && (this._flashOn = !0, this._active && !this._paused)) try { if (!await this.hasFlash()) throw "No flash available"; await this.$video.srcObject.getVideoTracks()[0].applyConstraints({ advanced: [{ torch: !0 }] }) } catch (a) { throw this._flashOn = !1, a; }
|
||||
} async turnFlashOff() { this._flashOn && (this._flashOn = !1, await this._restartVideoStream()) } destroy() {
|
||||
this.$video.removeEventListener("loadedmetadata", this._onLoadedMetaData); this.$video.removeEventListener("play", this._onPlay); document.removeEventListener("visibilitychange",
|
||||
this._onVisibilityChange); window.removeEventListener("resize", this._updateOverlay); this._destroyed = !0; this._flashOn = !1; this.stop(); e._postWorkerMessage(this._qrEnginePromise, "close")
|
||||
} async start() {
|
||||
if (this._destroyed) throw Error("The QR scanner can not be started as it had been destroyed."); if (!this._active || this._paused) if ("https:" !== window.location.protocol && console.warn("The camera stream is only accessible if the page is transferred via https."), this._active = !0, !document.hidden) if (this._paused =
|
||||
!1, this.$video.srcObject) await this.$video.play(); else try { let { stream: a, facingMode: b } = await this._getCameraStream(); !this._active || this._paused ? e._stopVideoStream(a) : (this._setVideoMirror(b), this.$video.srcObject = a, await this.$video.play(), this._flashOn && (this._flashOn = !1, this.turnFlashOn().catch(() => { }))) } catch (a) { if (!this._paused) throw this._active = !1, a; }
|
||||
} stop() { this.pause(); this._active = !1 } async pause(a = !1) {
|
||||
this._paused = !0; if (!this._active) return !0; this.$video.pause(); this.$overlay && (this.$overlay.style.display =
|
||||
"none"); let b = () => { this.$video.srcObject instanceof MediaStream && (e._stopVideoStream(this.$video.srcObject), this.$video.srcObject = null) }; if (a) return b(), !0; await new Promise(c => setTimeout(c, 300)); if (!this._paused) return !1; b(); return !0
|
||||
} async setCamera(a) { a !== this._preferredCamera && (this._preferredCamera = a, await this._restartVideoStream()) } static async scanImage(a, b, c, d, f = !1, h = !1) {
|
||||
let m, n = !1; b && ("scanRegion" in b || "qrEngine" in b || "canvas" in b || "disallowCanvasResizing" in b || "alsoTryWithoutScanRegion" in
|
||||
b || "returnDetailedScanResult" in b) ? (m = b.scanRegion, c = b.qrEngine, d = b.canvas, f = b.disallowCanvasResizing || !1, h = b.alsoTryWithoutScanRegion || !1, n = !0) : b || c || d || f || h ? console.warn("You're using a deprecated api for scanImage which will be removed in the future.") : console.warn("Note that the return type of scanImage will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true."); b = !!c; try {
|
||||
let p, k;[c, p] = await Promise.all([c || e.createQrEngine(), e._loadImage(a)]);
|
||||
[d, k] = e._drawToCanvas(p, m, d, f); let q; if (c instanceof Worker) {
|
||||
let g = c; b || e._postWorkerMessageSync(g, "inversionMode", "both"); q = await new Promise((l, v) => {
|
||||
let w, u, r, y = -1; u = t => { t.data.id === y && (g.removeEventListener("message", u), g.removeEventListener("error", r), clearTimeout(w), null !== t.data.data ? l({ data: t.data.data, cornerPoints: e._convertPoints(t.data.cornerPoints, m) }) : v(e.NO_QR_CODE_FOUND)) }; r = t => {
|
||||
g.removeEventListener("message", u); g.removeEventListener("error", r); clearTimeout(w); v("Scanner error: " + (t ?
|
||||
t.message || t : "Unknown Error"))
|
||||
}; g.addEventListener("message", u); g.addEventListener("error", r); w = setTimeout(() => r("timeout"), 1E4); let x = k.getImageData(0, 0, d.width, d.height); y = e._postWorkerMessageSync(g, "decode", x, [x.data.buffer])
|
||||
})
|
||||
} else q = await Promise.race([new Promise((g, l) => window.setTimeout(() => l("Scanner error: timeout"), 1E4)), (async () => {
|
||||
try { var [g] = await c.detect(d); if (!g) throw e.NO_QR_CODE_FOUND; return { data: g.rawValue, cornerPoints: e._convertPoints(g.cornerPoints, m) } } catch (l) {
|
||||
g = l.message || l;
|
||||
if (/not implemented|service unavailable/.test(g)) return e._disableBarcodeDetector = !0, e.scanImage(a, { scanRegion: m, canvas: d, disallowCanvasResizing: f, alsoTryWithoutScanRegion: h }); throw `Scanner error: ${g}`;
|
||||
}
|
||||
})()]); return n ? q : q.data
|
||||
} catch (p) { if (!m || !h) throw p; let k = await e.scanImage(a, { qrEngine: c, canvas: d, disallowCanvasResizing: f }); return n ? k : k.data } finally { b || e._postWorkerMessage(c, "close") }
|
||||
} setGrayscaleWeights(a, b, c, d = !0) {
|
||||
e._postWorkerMessage(this._qrEnginePromise, "grayscaleWeights", {
|
||||
red: a, green: b,
|
||||
blue: c, useIntegerApproximation: d
|
||||
})
|
||||
} setInversionMode(a) { e._postWorkerMessage(this._qrEnginePromise, "inversionMode", a) } static async createQrEngine(a) {
|
||||
a && console.warn("Specifying a worker path is not required and not supported anymore."); a = () => import("./qr-scanner-worker.min.js").then(c => c.createWorker()); if (!(!e._disableBarcodeDetector && "BarcodeDetector" in window && BarcodeDetector.getSupportedFormats && (await BarcodeDetector.getSupportedFormats()).includes("qr_code"))) return a(); let b = navigator.userAgentData;
|
||||
return b && b.brands.some(({ brand: c }) => /Chromium/i.test(c)) && /mac ?OS/i.test(b.platform) && await b.getHighEntropyValues(["architecture", "platformVersion"]).then(({ architecture: c, platformVersion: d }) => /arm/i.test(c || "arm") && 13 <= parseInt(d || "13")).catch(() => !0) ? a() : new BarcodeDetector({ formats: ["qr_code"] })
|
||||
} _onPlay() { this._scanRegion = this._calculateScanRegion(this.$video); this._updateOverlay(); this.$overlay && (this.$overlay.style.display = ""); this._scanFrame() } _onLoadedMetaData() {
|
||||
this._scanRegion = this._calculateScanRegion(this.$video);
|
||||
this._updateOverlay()
|
||||
} _onVisibilityChange() { document.hidden ? this.pause() : this._active && this.start() } _calculateScanRegion(a) { let b = Math.round(2 / 3 * Math.min(a.videoWidth, a.videoHeight)); return { x: Math.round((a.videoWidth - b) / 2), y: Math.round((a.videoHeight - b) / 2), width: b, height: b, downScaledWidth: this._legacyCanvasSize, downScaledHeight: this._legacyCanvasSize } } _updateOverlay() {
|
||||
requestAnimationFrame(() => {
|
||||
if (this.$overlay) {
|
||||
var a = this.$video, b = a.videoWidth, c = a.videoHeight, d = a.offsetWidth, f = a.offsetHeight, h = a.offsetLeft,
|
||||
m = a.offsetTop, n = window.getComputedStyle(a), p = n.objectFit, k = b / c, q = d / f; switch (p) { case "none": var g = b; var l = c; break; case "fill": g = d; l = f; break; default: ("cover" === p ? k > q : k < q) ? (l = f, g = l * k) : (g = d, l = g / k), "scale-down" === p && (g = Math.min(g, b), l = Math.min(l, c)) }var [v, w] = n.objectPosition.split(" ").map((r, y) => { const x = parseFloat(r); return r.endsWith("%") ? (y ? f - l : d - g) * x / 100 : x }); n = this._scanRegion.width || b; q = this._scanRegion.height || c; p = this._scanRegion.x || 0; var u = this._scanRegion.y || 0; k = this.$overlay.style; k.width =
|
||||
`${n / b * g}px`; k.height = `${q / c * l}px`; k.top = `${m + w + u / c * l}px`; c = /scaleX\(-1\)/.test(a.style.transform); k.left = `${h + (c ? d - v - g : v) + (c ? b - p - n : p) / b * g}px`; k.transform = a.style.transform
|
||||
}
|
||||
})
|
||||
} static _convertPoints(a, b) { if (!b) return a; let c = b.x || 0, d = b.y || 0, f = b.width && b.downScaledWidth ? b.width / b.downScaledWidth : 1; b = b.height && b.downScaledHeight ? b.height / b.downScaledHeight : 1; for (let h of a) h.x = h.x * f + c, h.y = h.y * b + d; return a } _scanFrame() {
|
||||
!this._active || this.$video.paused || this.$video.ended || ("requestVideoFrameCallback" in
|
||||
this.$video ? this.$video.requestVideoFrameCallback.bind(this.$video) : requestAnimationFrame)(async () => {
|
||||
if (!(1 >= this.$video.readyState)) {
|
||||
var a = Date.now() - this._lastScanTimestamp, b = 1E3 / this._maxScansPerSecond; a < b && await new Promise(d => setTimeout(d, b - a)); this._lastScanTimestamp = Date.now(); try { var c = await e.scanImage(this.$video, { scanRegion: this._scanRegion, qrEngine: this._qrEnginePromise, canvas: this.$canvas }) } catch (d) { if (!this._active) return; this._onDecodeError(d) } !e._disableBarcodeDetector || await this._qrEnginePromise instanceof
|
||||
Worker || (this._qrEnginePromise = e.createQrEngine()); c ? (this._onDecode ? this._onDecode(c) : this._legacyOnDecode && this._legacyOnDecode(c.data), this.$codeOutlineHighlight && (clearTimeout(this._codeOutlineHighlightRemovalTimeout), this._codeOutlineHighlightRemovalTimeout = void 0, this.$codeOutlineHighlight.setAttribute("viewBox", `${this._scanRegion.x || 0} ` + `${this._scanRegion.y || 0} ` + `${this._scanRegion.width || this.$video.videoWidth} ` + `${this._scanRegion.height || this.$video.videoHeight}`), this.$codeOutlineHighlight.firstElementChild.setAttribute("points",
|
||||
c.cornerPoints.map(({ x: d, y: f }) => `${d},${f}`).join(" ")), this.$codeOutlineHighlight.style.display = "")) : this.$codeOutlineHighlight && !this._codeOutlineHighlightRemovalTimeout && (this._codeOutlineHighlightRemovalTimeout = setTimeout(() => this.$codeOutlineHighlight.style.display = "none", 100))
|
||||
} this._scanFrame()
|
||||
"use strict";
|
||||
(function (e, a) {
|
||||
"object" === typeof exports && "undefined" !== typeof module
|
||||
? (module.exports = a())
|
||||
: "function" === typeof define && define.amd
|
||||
? define(a)
|
||||
: ((e = "undefined" !== typeof globalThis ? globalThis : e || self),
|
||||
(e.QrScanner = a()));
|
||||
})(this, function () {
|
||||
class e {
|
||||
constructor(a, b, c, d, f) {
|
||||
this._legacyCanvasSize = e.DEFAULT_CANVAS_SIZE;
|
||||
this._preferredCamera = "environment";
|
||||
this._maxScansPerSecond = 25;
|
||||
this._lastScanTimestamp = -1;
|
||||
this._destroyed = this._flashOn = this._paused = this._active = !1;
|
||||
this.$video = a;
|
||||
this.$canvas = document.createElement("canvas");
|
||||
c && "object" === typeof c
|
||||
? (this._onDecode = b)
|
||||
: (c || d || f
|
||||
? console.warn(
|
||||
"You're using a deprecated version of the QrScanner constructor which will be removed in the future"
|
||||
)
|
||||
: console.warn(
|
||||
"Note that the type of the scan result passed to onDecode will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true."
|
||||
),
|
||||
(this._legacyOnDecode = b));
|
||||
b = "object" === typeof c ? c : {};
|
||||
this._onDecodeError =
|
||||
b.onDecodeError || ("function" === typeof c ? c : this._onDecodeError);
|
||||
this._calculateScanRegion =
|
||||
b.calculateScanRegion ||
|
||||
("function" === typeof d ? d : this._calculateScanRegion);
|
||||
this._preferredCamera = b.preferredCamera || f || this._preferredCamera;
|
||||
this._legacyCanvasSize =
|
||||
"number" === typeof c
|
||||
? c
|
||||
: "number" === typeof d
|
||||
? d
|
||||
: this._legacyCanvasSize;
|
||||
this._maxScansPerSecond = b.maxScansPerSecond || this._maxScansPerSecond;
|
||||
this._onPlay = this._onPlay.bind(this);
|
||||
this._onLoadedMetaData = this._onLoadedMetaData.bind(this);
|
||||
this._onVisibilityChange = this._onVisibilityChange.bind(this);
|
||||
this._updateOverlay = this._updateOverlay.bind(this);
|
||||
a.disablePictureInPicture = !0;
|
||||
a.playsInline = !0;
|
||||
a.muted = !0;
|
||||
let h = !1;
|
||||
a.hidden && ((a.hidden = !1), (h = !0));
|
||||
document.body.contains(a) || (document.body.appendChild(a), (h = !0));
|
||||
c = a.parentElement;
|
||||
if (b.highlightScanRegion || b.highlightCodeOutline) {
|
||||
d = !!b.overlay;
|
||||
this.$overlay = b.overlay || document.createElement("div");
|
||||
f = this.$overlay.style;
|
||||
f.position = "absolute";
|
||||
f.display = "none";
|
||||
f.pointerEvents = "none";
|
||||
this.$overlay.classList.add("scan-region-highlight");
|
||||
if (!d && b.highlightScanRegion) {
|
||||
this.$overlay.innerHTML =
|
||||
'<svg class="scan-region-highlight-svg" viewBox="0 0 238 238" preserveAspectRatio="none" style="position:absolute;width:100%;height:100%;left:0;top:0;fill:none;stroke:#e9b213;stroke-width:4;stroke-linecap:round;stroke-linejoin:round"><path d="M31 2H10a8 8 0 0 0-8 8v21M207 2h21a8 8 0 0 1 8 8v21m0 176v21a8 8 0 0 1-8 8h-21m-176 0H10a8 8 0 0 1-8-8v-21"/></svg>';
|
||||
try {
|
||||
this.$overlay.firstElementChild.animate(
|
||||
{ transform: ["scale(.98)", "scale(1.01)"] },
|
||||
{
|
||||
duration: 400,
|
||||
iterations: Infinity,
|
||||
direction: "alternate",
|
||||
easing: "ease-in-out",
|
||||
}
|
||||
);
|
||||
} catch (m) {}
|
||||
c.insertBefore(this.$overlay, this.$video.nextSibling);
|
||||
}
|
||||
b.highlightCodeOutline &&
|
||||
(this.$overlay.insertAdjacentHTML(
|
||||
"beforeend",
|
||||
'<svg class="code-outline-highlight" preserveAspectRatio="none" style="display:none;width:100%;height:100%;fill:none;stroke:#e9b213;stroke-width:5;stroke-dasharray:25;stroke-linecap:round;stroke-linejoin:round"><polygon/></svg>'
|
||||
),
|
||||
(this.$codeOutlineHighlight = this.$overlay.lastElementChild));
|
||||
}
|
||||
this._scanRegion = this._calculateScanRegion(a);
|
||||
requestAnimationFrame(() => {
|
||||
let m = window.getComputedStyle(a);
|
||||
"none" === m.display &&
|
||||
(a.style.setProperty("display", "block", "important"), (h = !0));
|
||||
"visible" !== m.visibility &&
|
||||
(a.style.setProperty("visibility", "visible", "important"), (h = !0));
|
||||
h &&
|
||||
(console.warn(
|
||||
"QrScanner has overwritten the video hiding style to avoid Safari stopping the playback."
|
||||
),
|
||||
(a.style.opacity = "0"),
|
||||
(a.style.width = "0"),
|
||||
(a.style.height = "0"),
|
||||
this.$overlay &&
|
||||
this.$overlay.parentElement &&
|
||||
this.$overlay.parentElement.removeChild(this.$overlay),
|
||||
delete this.$overlay,
|
||||
delete this.$codeOutlineHighlight);
|
||||
this.$overlay && this._updateOverlay();
|
||||
});
|
||||
a.addEventListener("play", this._onPlay);
|
||||
a.addEventListener("loadedmetadata", this._onLoadedMetaData);
|
||||
document.addEventListener("visibilitychange", this._onVisibilityChange);
|
||||
window.addEventListener("resize", this._updateOverlay);
|
||||
this._qrEnginePromise = e.createQrEngine();
|
||||
}
|
||||
static set WORKER_PATH(a) {
|
||||
console.warn(
|
||||
"Setting QrScanner.WORKER_PATH is not required and not supported anymore. Have a look at the README for new setup instructions."
|
||||
);
|
||||
}
|
||||
static async hasCamera() {
|
||||
try {
|
||||
return !!(await e.listCameras(!1)).length;
|
||||
} catch (a) {
|
||||
return !1;
|
||||
}
|
||||
}
|
||||
static async listCameras(a = !1) {
|
||||
if (!navigator.mediaDevices) return [];
|
||||
let b = async () =>
|
||||
(await navigator.mediaDevices.enumerateDevices()).filter(
|
||||
(d) => "videoinput" === d.kind
|
||||
),
|
||||
c;
|
||||
try {
|
||||
a &&
|
||||
(await b()).every((d) => !d.label) &&
|
||||
(c = await navigator.mediaDevices.getUserMedia({
|
||||
audio: !1,
|
||||
video: !0,
|
||||
}));
|
||||
} catch (d) {}
|
||||
try {
|
||||
return (await b()).map((d, f) => ({
|
||||
id: d.deviceId,
|
||||
label: d.label || (0 === f ? "Default Camera" : `Camera ${f + 1}`),
|
||||
}));
|
||||
} finally {
|
||||
c &&
|
||||
(console.warn(
|
||||
"Call listCameras after successfully starting a QR scanner to avoid creating a temporary video stream"
|
||||
),
|
||||
e._stopVideoStream(c));
|
||||
}
|
||||
}
|
||||
async hasFlash() {
|
||||
let a;
|
||||
try {
|
||||
if (this.$video.srcObject) {
|
||||
if (!(this.$video.srcObject instanceof MediaStream)) return !1;
|
||||
a = this.$video.srcObject;
|
||||
} else a = (await this._getCameraStream()).stream;
|
||||
return "torch" in a.getVideoTracks()[0].getSettings();
|
||||
} catch (b) {
|
||||
return !1;
|
||||
} finally {
|
||||
a &&
|
||||
a !== this.$video.srcObject &&
|
||||
(console.warn(
|
||||
"Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream"
|
||||
),
|
||||
e._stopVideoStream(a));
|
||||
}
|
||||
}
|
||||
isFlashOn() {
|
||||
return this._flashOn;
|
||||
}
|
||||
async toggleFlash() {
|
||||
this._flashOn ? await this.turnFlashOff() : await this.turnFlashOn();
|
||||
}
|
||||
async turnFlashOn() {
|
||||
if (
|
||||
!this._flashOn &&
|
||||
!this._destroyed &&
|
||||
((this._flashOn = !0), this._active && !this._paused)
|
||||
)
|
||||
try {
|
||||
if (!(await this.hasFlash())) throw "No flash available";
|
||||
await this.$video.srcObject
|
||||
.getVideoTracks()[0]
|
||||
.applyConstraints({ advanced: [{ torch: !0 }] });
|
||||
} catch (a) {
|
||||
throw ((this._flashOn = !1), a);
|
||||
}
|
||||
}
|
||||
async turnFlashOff() {
|
||||
this._flashOn && ((this._flashOn = !1), await this._restartVideoStream());
|
||||
}
|
||||
destroy() {
|
||||
this.$video.removeEventListener("loadedmetadata", this._onLoadedMetaData);
|
||||
this.$video.removeEventListener("play", this._onPlay);
|
||||
document.removeEventListener(
|
||||
"visibilitychange",
|
||||
this._onVisibilityChange
|
||||
);
|
||||
window.removeEventListener("resize", this._updateOverlay);
|
||||
this._destroyed = !0;
|
||||
this._flashOn = !1;
|
||||
this.stop();
|
||||
e._postWorkerMessage(this._qrEnginePromise, "close");
|
||||
}
|
||||
async start() {
|
||||
if (this._destroyed)
|
||||
throw Error(
|
||||
"The QR scanner can not be started as it had been destroyed."
|
||||
);
|
||||
if (!this._active || this._paused)
|
||||
if (
|
||||
("https:" !== window.location.protocol &&
|
||||
console.warn(
|
||||
"The camera stream is only accessible if the page is transferred via https."
|
||||
),
|
||||
(this._active = !0),
|
||||
!document.hidden)
|
||||
)
|
||||
if (((this._paused = !1), this.$video.srcObject))
|
||||
await this.$video.play();
|
||||
else
|
||||
try {
|
||||
let { stream: a, facingMode: b } = await this._getCameraStream();
|
||||
!this._active || this._paused
|
||||
? e._stopVideoStream(a)
|
||||
: (this._setVideoMirror(b),
|
||||
(this.$video.srcObject = a),
|
||||
await this.$video.play(),
|
||||
this._flashOn &&
|
||||
((this._flashOn = !1), this.turnFlashOn().catch(() => {})));
|
||||
} catch (a) {
|
||||
if (!this._paused) throw ((this._active = !1), a);
|
||||
}
|
||||
}
|
||||
stop() {
|
||||
this.pause();
|
||||
this._active = !1;
|
||||
}
|
||||
async pause(a = !1) {
|
||||
this._paused = !0;
|
||||
if (!this._active) return !0;
|
||||
this.$video.pause();
|
||||
this.$overlay && (this.$overlay.style.display = "none");
|
||||
let b = () => {
|
||||
this.$video.srcObject instanceof MediaStream &&
|
||||
(e._stopVideoStream(this.$video.srcObject),
|
||||
(this.$video.srcObject = null));
|
||||
};
|
||||
if (a) return b(), !0;
|
||||
await new Promise((c) => setTimeout(c, 300));
|
||||
if (!this._paused) return !1;
|
||||
b();
|
||||
return !0;
|
||||
}
|
||||
async setCamera(a) {
|
||||
a !== this._preferredCamera &&
|
||||
((this._preferredCamera = a), await this._restartVideoStream());
|
||||
}
|
||||
static async scanImage(a, b, c, d, f = !1, h = !1) {
|
||||
let m,
|
||||
n = !1;
|
||||
b &&
|
||||
("scanRegion" in b ||
|
||||
"qrEngine" in b ||
|
||||
"canvas" in b ||
|
||||
"disallowCanvasResizing" in b ||
|
||||
"alsoTryWithoutScanRegion" in b ||
|
||||
"returnDetailedScanResult" in b)
|
||||
? ((m = b.scanRegion),
|
||||
(c = b.qrEngine),
|
||||
(d = b.canvas),
|
||||
(f = b.disallowCanvasResizing || !1),
|
||||
(h = b.alsoTryWithoutScanRegion || !1),
|
||||
(n = !0))
|
||||
: b || c || d || f || h
|
||||
? console.warn(
|
||||
"You're using a deprecated api for scanImage which will be removed in the future."
|
||||
)
|
||||
: console.warn(
|
||||
"Note that the return type of scanImage will change in the future. To already switch to the new api today, you can pass returnDetailedScanResult: true."
|
||||
);
|
||||
b = !!c;
|
||||
try {
|
||||
let p, k;
|
||||
[c, p] = await Promise.all([c || e.createQrEngine(), e._loadImage(a)]);
|
||||
[d, k] = e._drawToCanvas(p, m, d, f);
|
||||
let q;
|
||||
if (c instanceof Worker) {
|
||||
let g = c;
|
||||
b || e._postWorkerMessageSync(g, "inversionMode", "both");
|
||||
q = await new Promise((l, v) => {
|
||||
let w,
|
||||
u,
|
||||
r,
|
||||
y = -1;
|
||||
u = (t) => {
|
||||
t.data.id === y &&
|
||||
(g.removeEventListener("message", u),
|
||||
g.removeEventListener("error", r),
|
||||
clearTimeout(w),
|
||||
null !== t.data.data
|
||||
? l({
|
||||
data: t.data.data,
|
||||
cornerPoints: e._convertPoints(t.data.cornerPoints, m),
|
||||
})
|
||||
} _onDecodeError(a) { a !== e.NO_QR_CODE_FOUND && console.log(a) } async _getCameraStream() {
|
||||
if (!navigator.mediaDevices) throw "Camera not found."; let a = /^(environment|user)$/.test(this._preferredCamera) ? "facingMode" :
|
||||
"deviceId", b = [{ width: { min: 1024 } }, { width: { min: 768 } }, {}], c = b.map(d => Object.assign({}, d, { [a]: { exact: this._preferredCamera } })); for (let d of [...c, ...b]) try { let f = await navigator.mediaDevices.getUserMedia({ video: d, audio: !1 }), h = this._getFacingMode(f) || (d.facingMode ? this._preferredCamera : "environment" === this._preferredCamera ? "user" : "environment"); return { stream: f, facingMode: h } } catch (f) { } throw "Camera not found.";
|
||||
} async _restartVideoStream() { let a = this._paused; await this.pause(!0) && !a && this._active && await this.start() } static _stopVideoStream(a) {
|
||||
for (let b of a.getTracks()) b.stop(),
|
||||
a.removeTrack(b)
|
||||
} _setVideoMirror(a) { this.$video.style.transform = "scaleX(" + ("user" === a ? -1 : 1) + ")" } _getFacingMode(a) { return (a = a.getVideoTracks()[0]) ? /rear|back|environment/i.test(a.label) ? "environment" : /front|user|face/i.test(a.label) ? "user" : null : null } static _drawToCanvas(a, b, c, d = !1) {
|
||||
c = c || document.createElement("canvas"); let f = b && b.x ? b.x : 0, h = b && b.y ? b.y : 0, m = b && b.width ? b.width : a.videoWidth || a.width, n = b && b.height ? b.height : a.videoHeight || a.height; d || (d = b && b.downScaledWidth ? b.downScaledWidth : m, b = b && b.downScaledHeight ?
|
||||
b.downScaledHeight : n, c.width !== d && (c.width = d), c.height !== b && (c.height = b)); b = c.getContext("2d", { alpha: !1 }); b.imageSmoothingEnabled = !1; b.drawImage(a, f, h, m, n, 0, 0, c.width, c.height); return [c, b]
|
||||
} static async _loadImage(a) {
|
||||
if (a instanceof Image) return await e._awaitImageLoad(a), a; if (a instanceof HTMLVideoElement || a instanceof HTMLCanvasElement || a instanceof SVGImageElement || "OffscreenCanvas" in window && a instanceof OffscreenCanvas || "ImageBitmap" in window && a instanceof ImageBitmap) return a; if (a instanceof
|
||||
File || a instanceof Blob || a instanceof URL || "string" === typeof a) { let b = new Image; b.src = a instanceof File || a instanceof Blob ? URL.createObjectURL(a) : a.toString(); try { return await e._awaitImageLoad(b), b } finally { (a instanceof File || a instanceof Blob) && URL.revokeObjectURL(b.src) } } else throw "Unsupported image type.";
|
||||
} static async _awaitImageLoad(a) {
|
||||
a.complete && 0 !== a.naturalWidth || await new Promise((b, c) => {
|
||||
let d = f => {
|
||||
a.removeEventListener("load", d); a.removeEventListener("error", d); f instanceof ErrorEvent ?
|
||||
c("Image load error") : b()
|
||||
}; a.addEventListener("load", d); a.addEventListener("error", d)
|
||||
})
|
||||
} static async _postWorkerMessage(a, b, c, d) { return e._postWorkerMessageSync(await a, b, c, d) } static _postWorkerMessageSync(a, b, c, d) { if (!(a instanceof Worker)) return -1; let f = e._workerMessageId++; a.postMessage({ id: f, type: b, data: c }, d); return f }
|
||||
} e.DEFAULT_CANVAS_SIZE = 400; e.NO_QR_CODE_FOUND = "No QR code found"; e._disableBarcodeDetector = !1; e._workerMessageId = 0; return e
|
||||
})
|
||||
: v(e.NO_QR_CODE_FOUND));
|
||||
};
|
||||
r = (t) => {
|
||||
g.removeEventListener("message", u);
|
||||
g.removeEventListener("error", r);
|
||||
clearTimeout(w);
|
||||
v("Scanner error: " + (t ? t.message || t : "Unknown Error"));
|
||||
};
|
||||
g.addEventListener("message", u);
|
||||
g.addEventListener("error", r);
|
||||
w = setTimeout(() => r("timeout"), 1e4);
|
||||
let x = k.getImageData(0, 0, d.width, d.height);
|
||||
y = e._postWorkerMessageSync(g, "decode", x, [x.data.buffer]);
|
||||
});
|
||||
} else
|
||||
q = await Promise.race([
|
||||
new Promise((g, l) =>
|
||||
window.setTimeout(() => l("Scanner error: timeout"), 1e4)
|
||||
),
|
||||
(async () => {
|
||||
try {
|
||||
var [g] = await c.detect(d);
|
||||
if (!g) throw e.NO_QR_CODE_FOUND;
|
||||
return {
|
||||
data: g.rawValue,
|
||||
cornerPoints: e._convertPoints(g.cornerPoints, m),
|
||||
};
|
||||
} catch (l) {
|
||||
g = l.message || l;
|
||||
if (/not implemented|service unavailable/.test(g))
|
||||
return (
|
||||
(e._disableBarcodeDetector = !0),
|
||||
e.scanImage(a, {
|
||||
scanRegion: m,
|
||||
canvas: d,
|
||||
disallowCanvasResizing: f,
|
||||
alsoTryWithoutScanRegion: h,
|
||||
})
|
||||
);
|
||||
throw `Scanner error: ${g}`;
|
||||
}
|
||||
})(),
|
||||
]);
|
||||
return n ? q : q.data;
|
||||
} catch (p) {
|
||||
if (!m || !h) throw p;
|
||||
let k = await e.scanImage(a, {
|
||||
qrEngine: c,
|
||||
canvas: d,
|
||||
disallowCanvasResizing: f,
|
||||
});
|
||||
return n ? k : k.data;
|
||||
} finally {
|
||||
b || e._postWorkerMessage(c, "close");
|
||||
}
|
||||
}
|
||||
setGrayscaleWeights(a, b, c, d = !0) {
|
||||
e._postWorkerMessage(this._qrEnginePromise, "grayscaleWeights", {
|
||||
red: a,
|
||||
green: b,
|
||||
blue: c,
|
||||
useIntegerApproximation: d,
|
||||
});
|
||||
}
|
||||
setInversionMode(a) {
|
||||
e._postWorkerMessage(this._qrEnginePromise, "inversionMode", a);
|
||||
}
|
||||
static async createQrEngine(a) {
|
||||
a &&
|
||||
console.warn(
|
||||
"Specifying a worker path is not required and not supported anymore."
|
||||
);
|
||||
a = () =>
|
||||
import("./qr-scanner-worker.min.js").then((c) => c.createWorker());
|
||||
if (
|
||||
!(
|
||||
!e._disableBarcodeDetector &&
|
||||
"BarcodeDetector" in window &&
|
||||
BarcodeDetector.getSupportedFormats &&
|
||||
(await BarcodeDetector.getSupportedFormats()).includes("qr_code")
|
||||
)
|
||||
)
|
||||
return a();
|
||||
let b = navigator.userAgentData;
|
||||
return b &&
|
||||
b.brands.some(({ brand: c }) => /Chromium/i.test(c)) &&
|
||||
/mac ?OS/i.test(b.platform) &&
|
||||
(await b
|
||||
.getHighEntropyValues(["architecture", "platformVersion"])
|
||||
.then(
|
||||
({ architecture: c, platformVersion: d }) =>
|
||||
/arm/i.test(c || "arm") && 13 <= parseInt(d || "13")
|
||||
)
|
||||
.catch(() => !0))
|
||||
? a()
|
||||
: new BarcodeDetector({ formats: ["qr_code"] });
|
||||
}
|
||||
_onPlay() {
|
||||
this._scanRegion = this._calculateScanRegion(this.$video);
|
||||
this._updateOverlay();
|
||||
this.$overlay && (this.$overlay.style.display = "");
|
||||
this._scanFrame();
|
||||
}
|
||||
_onLoadedMetaData() {
|
||||
this._scanRegion = this._calculateScanRegion(this.$video);
|
||||
this._updateOverlay();
|
||||
}
|
||||
_onVisibilityChange() {
|
||||
document.hidden ? this.pause() : this._active && this.start();
|
||||
}
|
||||
_calculateScanRegion(a) {
|
||||
let b = Math.round((2 / 3) * Math.min(a.videoWidth, a.videoHeight));
|
||||
return {
|
||||
x: Math.round((a.videoWidth - b) / 2),
|
||||
y: Math.round((a.videoHeight - b) / 2),
|
||||
width: b,
|
||||
height: b,
|
||||
downScaledWidth: this._legacyCanvasSize,
|
||||
downScaledHeight: this._legacyCanvasSize,
|
||||
};
|
||||
}
|
||||
_updateOverlay() {
|
||||
requestAnimationFrame(() => {
|
||||
if (this.$overlay) {
|
||||
var a = this.$video,
|
||||
b = a.videoWidth,
|
||||
c = a.videoHeight,
|
||||
d = a.offsetWidth,
|
||||
f = a.offsetHeight,
|
||||
h = a.offsetLeft,
|
||||
m = a.offsetTop,
|
||||
n = window.getComputedStyle(a),
|
||||
p = n.objectFit,
|
||||
k = b / c,
|
||||
q = d / f;
|
||||
switch (p) {
|
||||
case "none":
|
||||
var g = b;
|
||||
var l = c;
|
||||
break;
|
||||
case "fill":
|
||||
g = d;
|
||||
l = f;
|
||||
break;
|
||||
default:
|
||||
("cover" === p ? k > q : k < q)
|
||||
? ((l = f), (g = l * k))
|
||||
: ((g = d), (l = g / k)),
|
||||
"scale-down" === p &&
|
||||
((g = Math.min(g, b)), (l = Math.min(l, c)));
|
||||
}
|
||||
var [v, w] = n.objectPosition.split(" ").map((r, y) => {
|
||||
const x = parseFloat(r);
|
||||
return r.endsWith("%") ? ((y ? f - l : d - g) * x) / 100 : x;
|
||||
});
|
||||
n = this._scanRegion.width || b;
|
||||
q = this._scanRegion.height || c;
|
||||
p = this._scanRegion.x || 0;
|
||||
var u = this._scanRegion.y || 0;
|
||||
k = this.$overlay.style;
|
||||
k.width = `${(n / b) * g}px`;
|
||||
k.height = `${(q / c) * l}px`;
|
||||
k.top = `${m + w + (u / c) * l}px`;
|
||||
c = /scaleX\(-1\)/.test(a.style.transform);
|
||||
k.left = `${
|
||||
h + (c ? d - v - g : v) + ((c ? b - p - n : p) / b) * g
|
||||
}px`;
|
||||
k.transform = a.style.transform;
|
||||
}
|
||||
});
|
||||
}
|
||||
static _convertPoints(a, b) {
|
||||
if (!b) return a;
|
||||
let c = b.x || 0,
|
||||
d = b.y || 0,
|
||||
f = b.width && b.downScaledWidth ? b.width / b.downScaledWidth : 1;
|
||||
b = b.height && b.downScaledHeight ? b.height / b.downScaledHeight : 1;
|
||||
for (let h of a) (h.x = h.x * f + c), (h.y = h.y * b + d);
|
||||
return a;
|
||||
}
|
||||
_scanFrame() {
|
||||
!this._active ||
|
||||
this.$video.paused ||
|
||||
this.$video.ended ||
|
||||
("requestVideoFrameCallback" in this.$video
|
||||
? this.$video.requestVideoFrameCallback.bind(this.$video)
|
||||
: requestAnimationFrame)(async () => {
|
||||
if (!(1 >= this.$video.readyState)) {
|
||||
var a = Date.now() - this._lastScanTimestamp,
|
||||
b = 1e3 / this._maxScansPerSecond;
|
||||
a < b && (await new Promise((d) => setTimeout(d, b - a)));
|
||||
this._lastScanTimestamp = Date.now();
|
||||
try {
|
||||
var c = await e.scanImage(this.$video, {
|
||||
scanRegion: this._scanRegion,
|
||||
qrEngine: this._qrEnginePromise,
|
||||
canvas: this.$canvas,
|
||||
});
|
||||
} catch (d) {
|
||||
if (!this._active) return;
|
||||
this._onDecodeError(d);
|
||||
}
|
||||
!e._disableBarcodeDetector ||
|
||||
(await this._qrEnginePromise) instanceof Worker ||
|
||||
(this._qrEnginePromise = e.createQrEngine());
|
||||
c
|
||||
? (this._onDecode
|
||||
? this._onDecode(c)
|
||||
: this._legacyOnDecode && this._legacyOnDecode(c.data),
|
||||
this.$codeOutlineHighlight &&
|
||||
(clearTimeout(this._codeOutlineHighlightRemovalTimeout),
|
||||
(this._codeOutlineHighlightRemovalTimeout = void 0),
|
||||
this.$codeOutlineHighlight.setAttribute(
|
||||
"viewBox",
|
||||
`${this._scanRegion.x || 0} ` +
|
||||
`${this._scanRegion.y || 0} ` +
|
||||
`${this._scanRegion.width || this.$video.videoWidth} ` +
|
||||
`${this._scanRegion.height || this.$video.videoHeight}`
|
||||
),
|
||||
this.$codeOutlineHighlight.firstElementChild.setAttribute(
|
||||
"points",
|
||||
c.cornerPoints
|
||||
.map(({ x: d, y: f }) => `${d},${f}`)
|
||||
.join(" ")
|
||||
),
|
||||
(this.$codeOutlineHighlight.style.display = "")))
|
||||
: this.$codeOutlineHighlight &&
|
||||
!this._codeOutlineHighlightRemovalTimeout &&
|
||||
(this._codeOutlineHighlightRemovalTimeout = setTimeout(
|
||||
() => (this.$codeOutlineHighlight.style.display = "none"),
|
||||
100
|
||||
));
|
||||
}
|
||||
this._scanFrame();
|
||||
});
|
||||
}
|
||||
_onDecodeError(a) {
|
||||
a !== e.NO_QR_CODE_FOUND && console.log(a);
|
||||
}
|
||||
async _getCameraStream() {
|
||||
if (!navigator.mediaDevices) throw "Camera not found.";
|
||||
let a = /^(environment|user)$/.test(this._preferredCamera)
|
||||
? "facingMode"
|
||||
: "deviceId",
|
||||
b = [{ width: { min: 1024 } }, { width: { min: 768 } }, {}],
|
||||
c = b.map((d) =>
|
||||
Object.assign({}, d, { [a]: { exact: this._preferredCamera } })
|
||||
);
|
||||
for (let d of [...c, ...b])
|
||||
try {
|
||||
let f = await navigator.mediaDevices.getUserMedia({
|
||||
video: d,
|
||||
audio: !1,
|
||||
}),
|
||||
h =
|
||||
this._getFacingMode(f) ||
|
||||
(d.facingMode
|
||||
? this._preferredCamera
|
||||
: "environment" === this._preferredCamera
|
||||
? "user"
|
||||
: "environment");
|
||||
return { stream: f, facingMode: h };
|
||||
} catch (f) {}
|
||||
throw "Camera not found.";
|
||||
}
|
||||
async _restartVideoStream() {
|
||||
let a = this._paused;
|
||||
(await this.pause(!0)) && !a && this._active && (await this.start());
|
||||
}
|
||||
static _stopVideoStream(a) {
|
||||
for (let b of a.getTracks()) b.stop(), a.removeTrack(b);
|
||||
}
|
||||
_setVideoMirror(a) {
|
||||
this.$video.style.transform = "scaleX(" + ("user" === a ? -1 : 1) + ")";
|
||||
}
|
||||
_getFacingMode(a) {
|
||||
return (a = a.getVideoTracks()[0])
|
||||
? /rear|back|environment/i.test(a.label)
|
||||
? "environment"
|
||||
: /front|user|face/i.test(a.label)
|
||||
? "user"
|
||||
: null
|
||||
: null;
|
||||
}
|
||||
static _drawToCanvas(a, b, c, d = !1) {
|
||||
c = c || document.createElement("canvas");
|
||||
let f = b && b.x ? b.x : 0,
|
||||
h = b && b.y ? b.y : 0,
|
||||
m = b && b.width ? b.width : a.videoWidth || a.width,
|
||||
n = b && b.height ? b.height : a.videoHeight || a.height;
|
||||
d ||
|
||||
((d = b && b.downScaledWidth ? b.downScaledWidth : m),
|
||||
(b = b && b.downScaledHeight ? b.downScaledHeight : n),
|
||||
c.width !== d && (c.width = d),
|
||||
c.height !== b && (c.height = b));
|
||||
b = c.getContext("2d", { alpha: !1 });
|
||||
b.imageSmoothingEnabled = !1;
|
||||
b.drawImage(a, f, h, m, n, 0, 0, c.width, c.height);
|
||||
return [c, b];
|
||||
}
|
||||
static async _loadImage(a) {
|
||||
if (a instanceof Image) return await e._awaitImageLoad(a), a;
|
||||
if (
|
||||
a instanceof HTMLVideoElement ||
|
||||
a instanceof HTMLCanvasElement ||
|
||||
a instanceof SVGImageElement ||
|
||||
("OffscreenCanvas" in window && a instanceof OffscreenCanvas) ||
|
||||
("ImageBitmap" in window && a instanceof ImageBitmap)
|
||||
)
|
||||
return a;
|
||||
if (
|
||||
a instanceof File ||
|
||||
a instanceof Blob ||
|
||||
a instanceof URL ||
|
||||
"string" === typeof a
|
||||
) {
|
||||
let b = new Image();
|
||||
b.src =
|
||||
a instanceof File || a instanceof Blob
|
||||
? URL.createObjectURL(a)
|
||||
: a.toString();
|
||||
try {
|
||||
return await e._awaitImageLoad(b), b;
|
||||
} finally {
|
||||
(a instanceof File || a instanceof Blob) &&
|
||||
URL.revokeObjectURL(b.src);
|
||||
}
|
||||
} else throw "Unsupported image type.";
|
||||
}
|
||||
static async _awaitImageLoad(a) {
|
||||
(a.complete && 0 !== a.naturalWidth) ||
|
||||
(await new Promise((b, c) => {
|
||||
let d = (f) => {
|
||||
a.removeEventListener("load", d);
|
||||
a.removeEventListener("error", d);
|
||||
f instanceof ErrorEvent ? c("Image load error") : b();
|
||||
};
|
||||
a.addEventListener("load", d);
|
||||
a.addEventListener("error", d);
|
||||
}));
|
||||
}
|
||||
static async _postWorkerMessage(a, b, c, d) {
|
||||
return e._postWorkerMessageSync(await a, b, c, d);
|
||||
}
|
||||
static _postWorkerMessageSync(a, b, c, d) {
|
||||
if (!(a instanceof Worker)) return -1;
|
||||
let f = e._workerMessageId++;
|
||||
a.postMessage({ id: f, type: b, data: c }, d);
|
||||
return f;
|
||||
}
|
||||
}
|
||||
e.DEFAULT_CANVAS_SIZE = 400;
|
||||
e.NO_QR_CODE_FOUND = "No QR code found";
|
||||
e._disableBarcodeDetector = !1;
|
||||
e._workerMessageId = 0;
|
||||
return e;
|
||||
});
|
||||
//# sourceMappingURL=qr-scanner.umd.min.js.map
|
||||
|
1
require/js/qr-scanner/qr-scanner.umd.min.js.map
Normal file
1
require/js/qr-scanner/qr-scanner.umd.min.js.map
Normal file
File diff suppressed because one or more lines are too long
65
require/js/qrscan.js
Normal file
65
require/js/qrscan.js
Normal file
@ -0,0 +1,65 @@
|
||||
import QrScanner from "./qr-scanner/qr-scanner.min.js";
|
||||
|
||||
const video = document.querySelector("video");
|
||||
const camList = document.querySelector("select");
|
||||
const fileSelector = document.querySelector("input[type='file']");
|
||||
|
||||
function scanAction(result) {
|
||||
console.log(result.data);
|
||||
}
|
||||
|
||||
function checkCam(hasCamera) {
|
||||
const errorP = document.querySelector("article p:first-of-type");
|
||||
if (!hasCamera) {
|
||||
errorP.innerHTML = "⚠️ Aucune caméra détectée !";
|
||||
}
|
||||
else {
|
||||
errorP.text = "";
|
||||
}
|
||||
}
|
||||
|
||||
const scanner = new QrScanner(
|
||||
video,
|
||||
result => scanAction(result),
|
||||
{
|
||||
highlightScanRegion: true,
|
||||
highlightCodeOutline: true,
|
||||
}
|
||||
);
|
||||
|
||||
window.scanner = scanner; // Débogage
|
||||
|
||||
|
||||
// Changement de caméra :
|
||||
camList.addEventListener("change", event => {
|
||||
scanner.setCamera(event.target.value);
|
||||
});
|
||||
|
||||
// Démarrage du scan :
|
||||
document.querySelector("button").addEventListener("click", () => {
|
||||
scanner.start().then(() => {
|
||||
QrScanner.listCameras(true).then(cameras => cameras.forEach(camera =>
|
||||
{
|
||||
const option = document.createElement("option");
|
||||
option.value = camera.id;
|
||||
option.text = camera.label;
|
||||
camList.add(option);
|
||||
}
|
||||
));
|
||||
});
|
||||
QrScanner.hasCamera().then(hasCamera => checkCam(hasCamera));
|
||||
});
|
||||
|
||||
// // Arrêt du scan :
|
||||
// document.querySelector("nav button:nth-of-type(2)").addEventListener("click", () => {
|
||||
// scanner.stop();
|
||||
// });
|
||||
|
||||
// Scan de fichier :
|
||||
fileSelector.addEventListener("change", () => {
|
||||
const file = fileSelector.files[0];
|
||||
if (file) {
|
||||
QrScanner.scanImage(file, {returnDetailedScanResult: true})
|
||||
.then(result => scanAction(result));
|
||||
}
|
||||
});
|
@ -5,12 +5,12 @@
|
||||
"article" => "",
|
||||
],
|
||||
"page_title" => [
|
||||
"home" => "",
|
||||
"home" => "Accueil",
|
||||
"article" => "",
|
||||
],
|
||||
"home" => [
|
||||
"subtitle" => "",
|
||||
"message" => "",
|
||||
"subtitle" => "Bienvenue !",
|
||||
"message" => "Page dédiée au test de scan de QR code.",
|
||||
"art_but" => "Voir ",
|
||||
],
|
||||
"nav" => [
|
||||
|
17
style.css
17
style.css
@ -252,8 +252,9 @@ main p {
|
||||
|
||||
main nav {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
/* grid-template-columns: repeat(3, 1fr); */
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
@ -266,8 +267,10 @@ main article p {
|
||||
}
|
||||
|
||||
main > article {
|
||||
/* display: flex;
|
||||
flex-direction: row; */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
gap: 15px;
|
||||
}
|
||||
|
||||
@ -299,6 +302,10 @@ section article {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
article p:first-of-type {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
footer {
|
||||
border-style: dashed;
|
||||
border-color: var(--bg-light);
|
||||
@ -312,6 +319,10 @@ footer > p {
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
video {
|
||||
max-height: 200px;
|
||||
}
|
||||
|
||||
.error {
|
||||
background: #cc0000 !important;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user