Provide more detailed scan results, for now adding qr location

Closes #66
Closes #109
This commit is contained in:
Daniel
2022-01-25 11:59:08 +01:00
parent 954d3e2930
commit 57b2a52734
13 changed files with 269 additions and 109 deletions
+17 -1
View File
@@ -6,7 +6,7 @@ In this library, several improvements have been applied over the original port:
- Web cam scanning support out of the box
- Uses the browser's native [BarcodeDetector](https://web.dev/shape-detection/) [if available](https://github.com/WICG/shape-detection-api#overview)
- Lightweight: ~53.1 kB (~14.1 kB gzipped) minified with Google's closure compiler. If the native `BarcodeDetector` is available, only ~9.4 kB (~3.6 kB gzipped) are loaded.
- Lightweight: ~54.9 kB (~14.7 kB gzipped) minified with Google's closure compiler. If the native `BarcodeDetector` is available, only ~11.0 kB (~4.1 kB gzipped) are loaded.
- Improved performance and reduced memory footprint.
- Runs in a WebWorker which keeps the main / UI thread responsive.
- Can be configured for better performance on colored QR codes.
@@ -151,9 +151,16 @@ Supported options are:
| `onDecodeError` | Handler to be invoked on decoding errors. The default is `QrScanner._onDecodeError`. |
| `preferredCamera` | Preference for the camera to be used. The preference can be either a device id as returned by `listCameras` or a facing mode specified as `'environment'` or `'user'`. The default is `'environment'`. Note that there is no guarantee that the preference can actually be fulfilled. |
| `calculateScanRegion` | A method that determines a region to which scanning should be restricted as a performance improvement. This region can optionally also be scaled down before performing the scan as an additional performance improvement. The region is specified as `x`, `y`, `width` and `height`; the dimensions for the downscaled region as `downScaledWidth` and `downScaledHeight`. Note that the aspect ratio between `width` and `height` and `downScaledWidth` and `downScaledHeight` should remain the same. By default, the scan region is restricted to a centered square of two thirds of the video width or height, whichever is smaller, and scaled down to a 400x400 square. |
| `returnDetailedScanResult` | Enforce reporting detailed scan results, see below. |
To use the default value for an option, omit it or supply `undefined`.
Results passed to the callback depend on whether an options object was provided:
- If no options object was provided, the result is a string with the read QR code's content. The simple string return type is for backwards compatibility, is now deprecated and will be removed in the future.
- If an options object was provided the result is an object with properties `data` which is the read QR code's string content and `cornerPoints` which are the corner points of the read QR code's outline on the camera stream.
To avoid usage of the deprecated api if you're not supplying any other options, you can supply `{ returnDetailedScanResult: true }` to enable the new api and get the detailed scan result.
#### 3. Start scanning
```js
qrScanner.start();
@@ -198,9 +205,18 @@ Supported options are:
| `canvas` | A manually created canvas to be reused. This improves performance if you're scanning a lot of images. A canvas can be manually created via a `<canvas>` tag in your markup or `document.createElement('canvas')`. By default, no canvas is reused for single image scanning. |
| `disallowCanvasResizing` | Request a provided canvas for reuse to not be resized, irrespective of the source image or source region dimensions. Note that the canvas and source region should have the same aspect ratio to avoid that the image to scan gets distorted which could make detecting QR codes impossible. By default, the canvas size is adapted to the scan region dimensions or down scaled scan region for single image scanning. |
| `alsoTryWithoutScanRegion` | Request a second scan on the entire image if a `scanRegion` was provided and no QR code was found within that region. By default, no second scan is attempted. |
| `returnDetailedScanResult` | Enforce reporting detailed scan results, see below. |
To use the default value for an option, omit it or supply `undefined`.
Returned results depend on whether an options object was provided:
- If no options object was provided, the result is a string with the read QR code's content. The simple string return type is for backwards compatibility, is now deprecated and will be removed in the future.
- If an options object was provided the result is an object with properties `data` which is the read QR code's string content and `cornerPoints` which are the corner points of the read QR code's outline on the camera stream.
To avoid usage of the deprecated api if you're not supplying any other options, you can supply `{ returnDetailedScanResult: true }` to enable the new api and get the detailed scan result.
If no QR code could be read, `scanImage` throws.
### Checking for Camera availability
This library provides a utility method for checking whether the device has a camera. This can be useful for determining whether to offer the QR web cam scanning functionality to a user.
+42 -5
View File
@@ -16,14 +16,25 @@
div {
margin-bottom: 16px;
}
#video-container {
position: relative;
}
#outline-canvas {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
}
#flash-toggle {
display: none;
}
</style>
<h1>Scan from WebCam:</h1>
<div>
<video id="qr-video"></video>
<br>
<div id="video-container">
<video id="qr-video"></video>
<canvas id="outline-canvas"></canvas>
</div>
<label>
<input id="show-scan-region" type="checkbox">
Show scan region
@@ -73,6 +84,8 @@
QrScanner.WORKER_PATH = '../qr-scanner-worker.min.js';
const video = document.getElementById('qr-video');
const outlineCanvas = document.getElementById('outline-canvas');
const outlineCanvasContext = outlineCanvas.getContext('2d');
const camHasCamera = document.getElementById('cam-has-camera');
const camList = document.getElementById('cam-list');
const camHasFlash = document.getElementById('cam-has-flash');
@@ -84,15 +97,32 @@
const fileQrResult = document.getElementById('file-qr-result');
function setResult(label, result) {
label.textContent = result;
console.log(result.data);
label.textContent = result.data;
camQrResultTimestamp.textContent = new Date().toString();
label.style.color = 'teal';
clearTimeout(label.highlightTimeout);
label.highlightTimeout = setTimeout(() => label.style.color = 'inherit', 100);
label.highlightTimeout = setTimeout(() => {
label.style.color = 'inherit';
outlineCanvasContext.clearRect(0, 0, outlineCanvas.width, outlineCanvas.height);
}, 100);
if (label === camQrResult) {
outlineCanvasContext.clearRect(0, 0, outlineCanvas.width, outlineCanvas.height);
outlineCanvasContext.beginPath();
outlineCanvasContext.moveTo(result.cornerPoints[0].x, result.cornerPoints[0].y);
outlineCanvasContext.lineTo(result.cornerPoints[1].x, result.cornerPoints[1].y);
outlineCanvasContext.lineTo(result.cornerPoints[2].x, result.cornerPoints[2].y);
outlineCanvasContext.lineTo(result.cornerPoints[3].x, result.cornerPoints[3].y);
outlineCanvasContext.closePath();
outlineCanvasContext.stroke();
}
}
// ####### Web Cam Scanning #######
outlineCanvasContext.strokeStyle = 'teal';
outlineCanvasContext.lineWidth = 5;
const scanner = new QrScanner(video, result => setResult(camQrResult, result), {
onDecodeError: error => {
camQrResult.textContent = error;
@@ -107,6 +137,13 @@
});
};
video.addEventListener('loadedmetadata', () => {
outlineCanvas.width = video.videoWidth;
outlineCanvas.height = video.videoHeight;
// copy the video's mirror
outlineCanvas.style.transform = video.style.transform;
});
scanner.start().then(() => {
updateFlashAvailability();
// List cameras after the scanner started to avoid listCamera's stream and the scanner's stream being requested
@@ -160,7 +197,7 @@
if (!file) {
return;
}
QrScanner.scanImage(file)
QrScanner.scanImage(file, { returnDetailedScanResult: true })
.then(result => setResult(fileQrResult, result))
.catch(e => setResult(fileQrResult, e || 'No QR code found.'));
});
+2 -2
View File
@@ -82,6 +82,6 @@ dataMask:3}},{bits:17913,formatInfo:{errorCorrectionLevel:1,dataMask:4}},{bits:1
formatInfo:{errorCorrectionLevel:0,dataMask:4}},{bits:25368,formatInfo:{errorCorrectionLevel:0,dataMask:5}},{bits:27713,formatInfo:{errorCorrectionLevel:0,dataMask:6}},{bits:26998,formatInfo:{errorCorrectionLevel:0,dataMask:7}},{bits:5769,formatInfo:{errorCorrectionLevel:3,dataMask:0}},{bits:5054,formatInfo:{errorCorrectionLevel:3,dataMask:1}},{bits:7399,formatInfo:{errorCorrectionLevel:3,dataMask:2}},{bits:6608,formatInfo:{errorCorrectionLevel:3,dataMask:3}},{bits:1890,formatInfo:{errorCorrectionLevel:3,
dataMask:4}},{bits:597,formatInfo:{errorCorrectionLevel:3,dataMask:5}},{bits:3340,formatInfo:{errorCorrectionLevel:3,dataMask:6}},{bits:2107,formatInfo:{errorCorrectionLevel:3,dataMask:7}},{bits:13663,formatInfo:{errorCorrectionLevel:2,dataMask:0}},{bits:12392,formatInfo:{errorCorrectionLevel:2,dataMask:1}},{bits:16177,formatInfo:{errorCorrectionLevel:2,dataMask:2}},{bits:14854,formatInfo:{errorCorrectionLevel:2,dataMask:3}},{bits:9396,formatInfo:{errorCorrectionLevel:2,dataMask:4}},{bits:8579,formatInfo:{errorCorrectionLevel:2,
dataMask:5}},{bits:11994,formatInfo:{errorCorrectionLevel:2,dataMask:6}},{bits:11245,formatInfo:{errorCorrectionLevel:2,dataMask:7}}],ha=[a=>0===(a.y+a.x)%2,a=>0===a.y%2,a=>0===a.x%3,a=>0===(a.y+a.x)%3,a=>0===(Math.floor(a.y/2)+Math.floor(a.x/3))%2,a=>0===a.x*a.y%2+a.x*a.y%3,a=>0===(a.y*a.x%2+a.y*a.x%3)%2,a=>0===((a.y+a.x)%2+a.y*a.x%3)%2],D=(a,b)=>Math.sqrt(Math.pow(b.x-a.x,2)+Math.pow(b.y-a.y,2)),ra={inversionAttempts:"attemptBoth",greyScaleWeights:{red:.2126,green:.7152,blue:.0722,useIntegerApproximation:!1},
canOverwriteImage:!0};O.default=O;let L="dontInvert",I={red:77,green:150,blue:29,useIntegerApproximation:!0};self.onmessage=a=>{let b=a.data.data;switch(a.data.type){case "decode":a=O(b.data,b.width,b.height,{inversionAttempts:L,greyScaleWeights:I});self.postMessage({type:"qrResult",data:a?a.data:null});break;case "grayscaleWeights":I.red=b.red;I.green=b.green;I.blue=b.blue;I.useIntegerApproximation=b.useIntegerApproximation;break;case "inversionMode":switch(b){case "original":L="dontInvert";break;
case "invert":L="onlyInvert";break;case "both":L="attemptBoth";break;default:throw Error("Invalid inversion mode");}break;case "close":self.close()}}})()
canOverwriteImage:!0};O.default=O;let L="dontInvert",I={red:77,green:150,blue:29,useIntegerApproximation:!0};self.onmessage=a=>{let b=a.data.data;switch(a.data.type){case "decode":(a=O(b.data,b.width,b.height,{inversionAttempts:L,greyScaleWeights:I}))?self.postMessage({type:"qrResult",data:a.data,cornerPoints:[a.location.topLeftCorner,a.location.topRightCorner,a.location.bottomRightCorner,a.location.bottomLeftCorner]}):self.postMessage({type:"qrResult",data:null});break;case "grayscaleWeights":I.red=
b.red;I.green=b.green;I.blue=b.blue;I.useIntegerApproximation=b.useIntegerApproximation;break;case "inversionMode":switch(b){case "original":L="dontInvert";break;case "invert":L="onlyInvert";break;case "both":L="attemptBoth";break;default:throw Error("Invalid inversion mode");}break;case "close":self.close()}}})()
//# sourceMappingURL=qr-scanner-worker.min.js.map
File diff suppressed because one or more lines are too long
+26 -25
View File
@@ -1,7 +1,7 @@
var c=c||{};c.scope={};c.ASSUME_ES5=!1;c.ASSUME_NO_NATIVE_MAP=!1;c.ASSUME_NO_NATIVE_SET=!1;c.SIMPLE_FROUND_POLYFILL=!1;c.ISOLATE_POLYFILLS=!1;c.FORCE_POLYFILL_PROMISE=!1;c.FORCE_POLYFILL_PROMISE_WHEN_NO_UNHANDLED_REJECTION=!1;c.defineProperty=c.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(a,b,d){if(a==Array.prototype||a==Object.prototype)return a;a[b]=d.value;return a};
c.getGlobal=function(a){a=["object"==typeof globalThis&&globalThis,a,"object"==typeof window&&window,"object"==typeof self&&self,"object"==typeof global&&global];for(var b=0;b<a.length;++b){var d=a[b];if(d&&d.Math==Math)return d}throw Error("Cannot find global object");};c.global=c.getGlobal(this);c.IS_SYMBOL_NATIVE="function"===typeof Symbol&&"symbol"===typeof Symbol("x");c.TRUST_ES6_POLYFILLS=!c.ISOLATE_POLYFILLS||c.IS_SYMBOL_NATIVE;c.polyfills={};c.propertyToPolyfillSymbol={};
c.POLYFILL_PREFIX="$jscp$";c.polyfill=function(a,b,d,e){b&&(c.ISOLATE_POLYFILLS?c.polyfillIsolated(a,b,d,e):c.polyfillUnisolated(a,b,d,e))};c.polyfillUnisolated=function(a,b){var d=c.global;a=a.split(".");for(var e=0;e<a.length-1;e++){var f=a[e];if(!(f in d))return;d=d[f]}a=a[a.length-1];e=d[a];b=b(e);b!=e&&null!=b&&c.defineProperty(d,a,{configurable:!0,writable:!0,value:b})};
c.polyfillIsolated=function(a,b,d){var e=a.split(".");a=1===e.length;var f=e[0];f=!a&&f in c.polyfills?c.polyfills:c.global;for(var h=0;h<e.length-1;h++){var k=e[h];if(!(k in f))return;f=f[k]}e=e[e.length-1];d=c.IS_SYMBOL_NATIVE&&"es6"===d?f[e]:null;b=b(d);null!=b&&(a?c.defineProperty(c.polyfills,e,{configurable:!0,writable:!0,value:b}):b!==d&&(void 0===c.propertyToPolyfillSymbol[e]&&(a=1E9*Math.random()>>>0,c.propertyToPolyfillSymbol[e]=c.IS_SYMBOL_NATIVE?c.global.Symbol(e):c.POLYFILL_PREFIX+a+"$"+
c.polyfillIsolated=function(a,b,d){var e=a.split(".");a=1===e.length;var f=e[0];f=!a&&f in c.polyfills?c.polyfills:c.global;for(var g=0;g<e.length-1;g++){var k=e[g];if(!(k in f))return;f=f[k]}e=e[e.length-1];d=c.IS_SYMBOL_NATIVE&&"es6"===d?f[e]:null;b=b(d);null!=b&&(a?c.defineProperty(c.polyfills,e,{configurable:!0,writable:!0,value:b}):b!==d&&(void 0===c.propertyToPolyfillSymbol[e]&&(a=1E9*Math.random()>>>0,c.propertyToPolyfillSymbol[e]=c.IS_SYMBOL_NATIVE?c.global.Symbol(e):c.POLYFILL_PREFIX+a+"$"+
e),c.defineProperty(f,c.propertyToPolyfillSymbol[e],{configurable:!0,writable:!0,value:b})))};c.underscoreProtoCanBeSet=function(){var a={a:!0},b={};try{return b.__proto__=a,b.a}catch(d){}return!1};c.setPrototypeOf=c.TRUST_ES6_POLYFILLS&&"function"==typeof Object.setPrototypeOf?Object.setPrototypeOf:c.underscoreProtoCanBeSet()?function(a,b){a.__proto__=b;if(a.__proto__!==b)throw new TypeError(a+" is not extensible");return a}:null;
c.arrayIteratorImpl=function(a){var b=0;return function(){return b<a.length?{done:!1,value:a[b++]}:{done:!0}}};c.arrayIterator=function(a){return{next:c.arrayIteratorImpl(a)}};c.makeIterator=function(a){var b="undefined"!=typeof Symbol&&Symbol.iterator&&a[Symbol.iterator];return b?b.call(a):c.arrayIterator(a)};c.generator={};c.generator.ensureIteratorResultIsObject_=function(a){if(!(a instanceof Object))throw new TypeError("Iterator result "+a+" is not an object");};
c.generator.Context=function(){this.isRunning_=!1;this.yieldAllIterator_=null;this.yieldResult=void 0;this.nextAddress=1;this.finallyAddress_=this.catchAddress_=0;this.finallyContexts_=this.abruptCompletion_=null};c.generator.Context.prototype.start_=function(){if(this.isRunning_)throw new TypeError("Generator is already running");this.isRunning_=!0};c.generator.Context.prototype.stop_=function(){this.isRunning_=!1};
@@ -13,30 +13,31 @@ c.generator.Context.prototype.leaveFinallyBlock=function(a,b){b=this.finallyCont
c.generator.Context.PropertyIterator=function(a){this.object_=a;this.properties_=[];for(var b in a)this.properties_.push(b);this.properties_.reverse()};c.generator.Context.PropertyIterator.prototype.getNext=function(){for(;0<this.properties_.length;){var a=this.properties_.pop();if(a in this.object_)return a}return null};c.generator.Engine_=function(a){this.context_=new c.generator.Context;this.program_=a};
c.generator.Engine_.prototype.next_=function(a){this.context_.start_();if(this.context_.yieldAllIterator_)return this.yieldAllStep_(this.context_.yieldAllIterator_.next,a,this.context_.next_);this.context_.next_(a);return this.nextStep_()};c.generator.Engine_.prototype.return_=function(a){this.context_.start_();var b=this.context_.yieldAllIterator_;if(b)return this.yieldAllStep_("return"in b?b["return"]:function(d){return{value:d,done:!0}},a,this.context_.return);this.context_.return(a);return this.nextStep_()};
c.generator.Engine_.prototype.throw_=function(a){this.context_.start_();if(this.context_.yieldAllIterator_)return this.yieldAllStep_(this.context_.yieldAllIterator_["throw"],a,this.context_.next_);this.context_.throw_(a);return this.nextStep_()};
c.generator.Engine_.prototype.yieldAllStep_=function(a,b,d){try{var e=a.call(this.context_.yieldAllIterator_,b);c.generator.ensureIteratorResultIsObject_(e);if(!e.done)return this.context_.stop_(),e;var f=e.value}catch(h){return this.context_.yieldAllIterator_=null,this.context_.throw_(h),this.nextStep_()}this.context_.yieldAllIterator_=null;d.call(this.context_,f);return this.nextStep_()};
c.generator.Engine_.prototype.yieldAllStep_=function(a,b,d){try{var e=a.call(this.context_.yieldAllIterator_,b);c.generator.ensureIteratorResultIsObject_(e);if(!e.done)return this.context_.stop_(),e;var f=e.value}catch(g){return this.context_.yieldAllIterator_=null,this.context_.throw_(g),this.nextStep_()}this.context_.yieldAllIterator_=null;d.call(this.context_,f);return this.nextStep_()};
c.generator.Engine_.prototype.nextStep_=function(){for(;this.context_.nextAddress;)try{var a=this.program_(this.context_);if(a)return this.context_.stop_(),{value:a.value,done:!1}}catch(b){this.context_.yieldResult=void 0,this.context_.throw_(b)}this.context_.stop_();if(this.context_.abruptCompletion_){a=this.context_.abruptCompletion_;this.context_.abruptCompletion_=null;if(a.isException)throw a.exception;return{value:a.return,done:!0}}return{value:void 0,done:!0}};
c.generator.Generator_=function(a){this.next=function(b){return a.next_(b)};this.throw=function(b){return a.throw_(b)};this.return=function(b){return a.return_(b)};this[Symbol.iterator]=function(){return this}};c.generator.createGenerator=function(a,b){b=new c.generator.Generator_(new c.generator.Engine_(b));c.setPrototypeOf&&a.prototype&&c.setPrototypeOf(b,a.prototype);return b};
c.asyncExecutePromiseGenerator=function(a){function b(e){return a.next(e)}function d(e){return a.throw(e)}return new Promise(function(e,f){function h(k){k.done?e(k.value):Promise.resolve(k.value).then(b,d).then(h,f)}h(a.next())})};c.asyncExecutePromiseGeneratorFunction=function(a){return c.asyncExecutePromiseGenerator(a())};c.asyncExecutePromiseGeneratorProgram=function(a){return c.asyncExecutePromiseGenerator(new c.generator.Generator_(new c.generator.Engine_(a)))};
class g{constructor(a,b,d,e,f){this._preferredCamera="environment";this._legacyCanvasSize=g.DEFAULT_CANVAS_SIZE;this._destroyed=this._flashOn=this._paused=this._active=!1;this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;(d&&"object"!==typeof d||e||f)&&console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future");b="object"===typeof d?d:{};this._onDecodeError=b.onDecodeError||("function"===typeof d?d:this._onDecodeError);
this._calculateScanRegion=b.calculateScanRegion||("function"===typeof e?e:this._calculateScanRegion);this._preferredCamera=b.preferredCamera||f||this._preferredCamera;this._legacyCanvasSize="number"===typeof d?d:"number"===typeof e?e:this._legacyCanvasSize;this._scanRegion=this._calculateScanRegion(a);this._onPlay=this._onPlay.bind(this);this._onLoadedMetaData=this._onLoadedMetaData.bind(this);this._onVisibilityChange=this._onVisibilityChange.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);requestAnimationFrame(()=>{let k=window.getComputedStyle(a);"none"===k.display&&(a.style.setProperty("display","block","important"),h=!0);"visible"!==k.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")});a.addEventListener("play",this._onPlay);a.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrEnginePromise=g.createQrEngine()}static hasCamera(){return c.asyncExecutePromiseGeneratorFunction(function*(){try{return!!(yield g.listCameras(!1)).length}catch(a){return!1}})}static listCameras(a=!1){return c.asyncExecutePromiseGeneratorFunction(function*(){if(!navigator.mediaDevices)return[];let b=()=>c.asyncExecutePromiseGeneratorFunction(function*(){return(yield navigator.mediaDevices.enumerateDevices()).filter(e=>
"videoinput"===e.kind)}),d;try{a&&(yield b()).every(e=>!e.label)&&(d=yield navigator.mediaDevices.getUserMedia({audio:!1,video:!0}))}catch(e){}try{return(yield b()).map((e,f)=>({id:e.deviceId,label:e.label||(0===f?"Default Camera":`Camera ${f+1}`)}))}finally{d&&(console.warn("Call listCameras after successfully starting a QR scanner to avoid creating a temporary video stream"),g._stopVideoStream(d))}})}hasFlash(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){let b;try{if(a.$video.srcObject){if(!(a.$video.srcObject instanceof
MediaStream))return!1;b=a.$video.srcObject}else b=(yield a._getCameraStream()).stream;return"torch"in b.getVideoTracks()[0].getSettings()}catch(d){return!1}finally{b&&b!==a.$video.srcObject&&(console.warn("Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream"),g._stopVideoStream(b))}})}isFlashOn(){return this._flashOn}toggleFlash(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){a._flashOn?yield a.turnFlashOff():yield a.turnFlashOn()})}turnFlashOn(){const a=
this;return c.asyncExecutePromiseGeneratorFunction(function*(){if(!a._flashOn&&!a._destroyed&&(a._flashOn=!0,a._active&&!a._paused))try{if(!(yield a.hasFlash()))throw"No flash available";yield a.$video.srcObject.getVideoTracks()[0].applyConstraints({advanced:[{torch:!0}]})}catch(b){throw a._flashOn=!1,b;}})}turnFlashOff(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){a._flashOn&&(a._flashOn=!1,yield a._restartVideoStream())})}destroy(){this.$video.removeEventListener("loadedmetadata",
this._onLoadedMetaData);this.$video.removeEventListener("play",this._onPlay);document.removeEventListener("visibilitychange",this._onVisibilityChange);this._destroyed=!0;this._flashOn=!1;this.stop();g._postWorkerMessage(this._qrEnginePromise,"close")}start(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){if(!(a._active&&!a._paused||a._destroyed||("https:"!==window.location.protocol&&console.warn("The camera stream is only accessible if the page is transferred via https."),
a._active=!0,document.hidden)))if(a._paused=!1,a.$video.srcObject)yield a.$video.play();else try{let {stream:b,facingMode:d}=yield a._getCameraStream();!a._active||a._paused?g._stopVideoStream(b):(a.$video.srcObject=b,yield a.$video.play(),a._setVideoMirror(d),a._flashOn&&(a._flashOn=!1,a.turnFlashOn().catch(()=>{})))}catch(b){if(!a._paused)throw a._active=!1,b;}})}stop(){this.pause();this._active=!1}pause(a=!1){const b=this;return c.asyncExecutePromiseGeneratorFunction(function*(){b._paused=!0;if(!b._active)return!0;
b.$video.pause();let d=()=>{b.$video.srcObject instanceof MediaStream&&(g._stopVideoStream(b.$video.srcObject),b.$video.srcObject=null)};if(a)return d(),!0;yield new Promise(e=>setTimeout(e,300));if(!b._paused)return!1;d();return!0})}setCamera(a){const b=this;return c.asyncExecutePromiseGeneratorFunction(function*(){a!==b._preferredCamera&&(b._preferredCamera=a,yield b._restartVideoStream())})}static scanImage(a,b,d,e,f=!1,h=!1){return c.asyncExecutePromiseGeneratorFunction(function*(){let k;b&&("scanRegion"in
b||"qrEngine"in b||"canvas"in b||"disallowCanvasResizing"in b||"alsoTryWithoutScanRegion"in b)?(k=b.scanRegion,d=b.qrEngine,e=b.canvas,f=b.disallowCanvasResizing||!1,h=b.alsoTryWithoutScanRegion||!1):(b||d||e||f||h)&&console.warn("You're using a deprecated api for scanImage which will be removed in the future.");let n=!!d;try{let q,v;[d,q]=yield Promise.all([d||g.createQrEngine(),g._loadImage(a)]);[e,v]=g._drawToCanvas(q,k,e,f);if(d instanceof Worker){let l=d;n||l.postMessage({type:"inversionMode",
data:"both"});return yield new Promise((t,w)=>{let u,r,p;r=m=>{"qrResult"===m.data.type&&(l.removeEventListener("message",r),l.removeEventListener("error",p),clearTimeout(u),null!==m.data.data?t(m.data.data):w(g.NO_QR_CODE_FOUND))};p=m=>{l.removeEventListener("message",r);l.removeEventListener("error",p);clearTimeout(u);w("Scanner error: "+(m?m.message||m:"Unknown Error"))};l.addEventListener("message",r);l.addEventListener("error",p);u=setTimeout(()=>p("timeout"),1E4);let x=v.getImageData(0,0,e.width,
e.height);l.postMessage({type:"decode",data:x},[x.data.buffer])})}return yield Promise.race([new Promise((l,t)=>window.setTimeout(()=>t("Scanner error: timeout"),1E4)),(()=>c.asyncExecutePromiseGeneratorFunction(function*(){try{let [l]=yield d.detect(e);if(!l)throw g.NO_QR_CODE_FOUND;return l.rawValue}catch(l){throw`Scanner error: ${l.message||l}`;}}))()])}catch(q){if(!k||!h)throw q;return yield g.scanImage(a,{qrEngine:d,canvas:e,disallowCanvasResizing:f})}finally{n||g._postWorkerMessage(d,"close")}})}setGrayscaleWeights(a,
b,d,e=!0){g._postWorkerMessage(this._qrEnginePromise,"grayscaleWeights",{red:a,green:b,blue:d,useIntegerApproximation:e})}setInversionMode(a){g._postWorkerMessage(this._qrEnginePromise,"inversionMode",a)}static createQrEngine(a=g.WORKER_PATH){return c.asyncExecutePromiseGeneratorFunction(function*(){return"BarcodeDetector"in window&&BarcodeDetector.getSupportedFormats&&(yield BarcodeDetector.getSupportedFormats()).includes("qr_code")?new BarcodeDetector({formats:["qr_code"]}):new Worker(a)})}_onPlay(){this._scanRegion=
this._calculateScanRegion(this.$video);this._scanFrame()}_onLoadedMetaData(){this._scanRegion=this._calculateScanRegion(this.$video)}_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}}_scanFrame(){!this._active||this.$video.paused||
this.$video.ended||requestAnimationFrame(()=>{const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){if(!(1>=a.$video.readyState)){try{var b=yield g.scanImage(a.$video,{scanRegion:a._scanRegion,qrEngine:a._qrEnginePromise,canvas:a.$canvas})}catch(d){if(!a._active)return;(d.message||d).includes("service unavailable")&&(a._qrEnginePromise=g.createQrEngine());a._onDecodeError(d)}b&&a._onDecode(b)}a._scanFrame()})})}_onDecodeError(a){a!==g.NO_QR_CODE_FOUND&&console.log(a)}_getCameraStream(){const a=
this;return c.asyncExecutePromiseGeneratorFunction(function*(){if(!navigator.mediaDevices)throw"Camera not found.";let b=/^(environment|user)$/.test(a._preferredCamera)?"facingMode":"deviceId",d=[{width:{min:1024}},{width:{min:768}},{}],e=d.map(f=>Object.assign({},f,{[b]:{exact:a._preferredCamera}}));for(let f of[...e,...d])try{let h=yield navigator.mediaDevices.getUserMedia({video:f,audio:!1}),k=a._getFacingMode(h)||(f.facingMode?a._preferredCamera:"environment"===a._preferredCamera?"user":"environment");
return{stream:h,facingMode:k}}catch(h){}throw"Camera not found.";})}_restartVideoStream(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){let b=a._paused;(yield a.pause(!0))&&!b&&a._active&&(yield a.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,d,e=!1){d=d||document.createElement("canvas");let f=b&&b.x?b.x:0,h=b&&b.y?b.y:0,k=b&&b.width?b.width:a.videoWidth||a.width,n=b&&b.height?b.height:a.videoHeight||a.height;e||(e=b&&b.downScaledWidth?b.downScaledWidth:k,b=b&&b.downScaledHeight?b.downScaledHeight:n,d.width!==e&&(d.width=e),d.height!==b&&(d.height=b));b=d.getContext("2d",{alpha:!1});b.imageSmoothingEnabled=!1;b.drawImage(a,f,h,k,n,0,0,d.width,d.height);return[d,b]}static _loadImage(a){return c.asyncExecutePromiseGeneratorFunction(function*(){if(a instanceof
Image)return yield g._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 yield g._awaitImageLoad(b),b}finally{(a instanceof File||a instanceof
Blob)&&URL.revokeObjectURL(b.src)}}else throw"Unsupported image type.";})}static _awaitImageLoad(a){return c.asyncExecutePromiseGeneratorFunction(function*(){a.complete&&0!==a.naturalWidth||(yield new Promise((b,d)=>{let e=f=>{a.removeEventListener("load",e);a.removeEventListener("error",e);f instanceof ErrorEvent?d("Image load error"):b()};a.addEventListener("load",e);a.addEventListener("error",e)}))})}static _postWorkerMessage(a,b,d){return c.asyncExecutePromiseGeneratorFunction(function*(){let e=
yield a;e instanceof Worker&&e.postMessage({type:b,data:d})})}}g.DEFAULT_CANVAS_SIZE=400;g.NO_QR_CODE_FOUND="No QR code found";g.WORKER_PATH="qr-scanner-worker.min.js";export default g
c.asyncExecutePromiseGenerator=function(a){function b(e){return a.next(e)}function d(e){return a.throw(e)}return new Promise(function(e,f){function g(k){k.done?e(k.value):Promise.resolve(k.value).then(b,d).then(g,f)}g(a.next())})};c.asyncExecutePromiseGeneratorFunction=function(a){return c.asyncExecutePromiseGenerator(a())};c.asyncExecutePromiseGeneratorProgram=function(a){return c.asyncExecutePromiseGenerator(new c.generator.Generator_(new c.generator.Engine_(a)))};
class h{constructor(a,b,d,e,f){this._legacyCanvasSize=h.DEFAULT_CANVAS_SIZE;this._preferredCamera="environment";this._destroyed=this._flashOn=this._paused=this._active=!1;this.$video=a;this.$canvas=document.createElement("canvas");d&&"object"===typeof d?this._onDecode=b:(d||e||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 d?d:{};this._onDecodeError=b.onDecodeError||("function"===typeof d?d:this._onDecodeError);this._calculateScanRegion=b.calculateScanRegion||("function"===typeof e?e:this._calculateScanRegion);this._preferredCamera=b.preferredCamera||f||this._preferredCamera;this._legacyCanvasSize="number"===typeof d?d:"number"===typeof e?e:this._legacyCanvasSize;this._scanRegion=this._calculateScanRegion(a);this._onPlay=this._onPlay.bind(this);this._onLoadedMetaData=this._onLoadedMetaData.bind(this);
this._onVisibilityChange=this._onVisibilityChange.bind(this);a.disablePictureInPicture=!0;a.playsInline=!0;a.muted=!0;let g=!1;a.hidden&&(a.hidden=!1,g=!0);document.body.contains(a)||(document.body.appendChild(a),g=!0);requestAnimationFrame(()=>{let k=window.getComputedStyle(a);"none"===k.display&&(a.style.setProperty("display","block","important"),g=!0);"visible"!==k.visibility&&(a.style.setProperty("visibility","visible","important"),g=!0);g&&(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")});a.addEventListener("play",this._onPlay);a.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrEnginePromise=h.createQrEngine()}static hasCamera(){return c.asyncExecutePromiseGeneratorFunction(function*(){try{return!!(yield h.listCameras(!1)).length}catch(a){return!1}})}static listCameras(a=!1){return c.asyncExecutePromiseGeneratorFunction(function*(){if(!navigator.mediaDevices)return[];
let b=()=>c.asyncExecutePromiseGeneratorFunction(function*(){return(yield navigator.mediaDevices.enumerateDevices()).filter(e=>"videoinput"===e.kind)}),d;try{a&&(yield b()).every(e=>!e.label)&&(d=yield navigator.mediaDevices.getUserMedia({audio:!1,video:!0}))}catch(e){}try{return(yield b()).map((e,f)=>({id:e.deviceId,label:e.label||(0===f?"Default Camera":`Camera ${f+1}`)}))}finally{d&&(console.warn("Call listCameras after successfully starting a QR scanner to avoid creating a temporary video stream"),
h._stopVideoStream(d))}})}hasFlash(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){let b;try{if(a.$video.srcObject){if(!(a.$video.srcObject instanceof MediaStream))return!1;b=a.$video.srcObject}else b=(yield a._getCameraStream()).stream;return"torch"in b.getVideoTracks()[0].getSettings()}catch(d){return!1}finally{b&&b!==a.$video.srcObject&&(console.warn("Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream"),h._stopVideoStream(b))}})}isFlashOn(){return this._flashOn}toggleFlash(){const a=
this;return c.asyncExecutePromiseGeneratorFunction(function*(){a._flashOn?yield a.turnFlashOff():yield a.turnFlashOn()})}turnFlashOn(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){if(!a._flashOn&&!a._destroyed&&(a._flashOn=!0,a._active&&!a._paused))try{if(!(yield a.hasFlash()))throw"No flash available";yield a.$video.srcObject.getVideoTracks()[0].applyConstraints({advanced:[{torch:!0}]})}catch(b){throw a._flashOn=!1,b;}})}turnFlashOff(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){a._flashOn&&
(a._flashOn=!1,yield a._restartVideoStream())})}destroy(){this.$video.removeEventListener("loadedmetadata",this._onLoadedMetaData);this.$video.removeEventListener("play",this._onPlay);document.removeEventListener("visibilitychange",this._onVisibilityChange);this._destroyed=!0;this._flashOn=!1;this.stop();h._postWorkerMessage(this._qrEnginePromise,"close")}start(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){if(!(a._active&&!a._paused||a._destroyed||("https:"!==window.location.protocol&&
console.warn("The camera stream is only accessible if the page is transferred via https."),a._active=!0,document.hidden)))if(a._paused=!1,a.$video.srcObject)yield a.$video.play();else try{let {stream:b,facingMode:d}=yield a._getCameraStream();!a._active||a._paused?h._stopVideoStream(b):(a._setVideoMirror(d),a.$video.srcObject=b,yield a.$video.play(),a._flashOn&&(a._flashOn=!1,a.turnFlashOn().catch(()=>{})))}catch(b){if(!a._paused)throw a._active=!1,b;}})}stop(){this.pause();this._active=!1}pause(a=
!1){const b=this;return c.asyncExecutePromiseGeneratorFunction(function*(){b._paused=!0;if(!b._active)return!0;b.$video.pause();let d=()=>{b.$video.srcObject instanceof MediaStream&&(h._stopVideoStream(b.$video.srcObject),b.$video.srcObject=null)};if(a)return d(),!0;yield new Promise(e=>setTimeout(e,300));if(!b._paused)return!1;d();return!0})}setCamera(a){const b=this;return c.asyncExecutePromiseGeneratorFunction(function*(){a!==b._preferredCamera&&(b._preferredCamera=a,yield b._restartVideoStream())})}static scanImage(a,
b,d,e,f=!1,g=!1){return c.asyncExecutePromiseGeneratorFunction(function*(){let k,n=!1;b&&("scanRegion"in b||"qrEngine"in b||"canvas"in b||"disallowCanvasResizing"in b||"alsoTryWithoutScanRegion"in b||"returnDetailedScanResult"in b)?(k=b.scanRegion,d=b.qrEngine,e=b.canvas,f=b.disallowCanvasResizing||!1,g=b.alsoTryWithoutScanRegion||!1,n=!0):b||d||e||f||g?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.");
let w=!!d;try{let r,p;[d,r]=yield Promise.all([d||h.createQrEngine(),h._loadImage(a)]);[e,p]=h._drawToCanvas(r,k,e,f);if(d instanceof Worker){let l=d;w||l.postMessage({type:"inversionMode",data:"both"});return yield new Promise((u,x)=>{let v,t,q;t=m=>{"qrResult"===m.data.type&&(l.removeEventListener("message",t),l.removeEventListener("error",q),clearTimeout(v),null!==m.data.data?u(n?{data:m.data.data,cornerPoints:h._convertPoints(m.data.cornerPoints,k)}:m.data.data):x(h.NO_QR_CODE_FOUND))};q=m=>{l.removeEventListener("message",
t);l.removeEventListener("error",q);clearTimeout(v);x("Scanner error: "+(m?m.message||m:"Unknown Error"))};l.addEventListener("message",t);l.addEventListener("error",q);v=setTimeout(()=>q("timeout"),1E4);let y=p.getImageData(0,0,e.width,e.height);l.postMessage({type:"decode",data:y},[y.data.buffer])})}return yield Promise.race([new Promise((l,u)=>window.setTimeout(()=>u("Scanner error: timeout"),1E4)),(()=>c.asyncExecutePromiseGeneratorFunction(function*(){try{let [l]=yield d.detect(e);if(!l)throw h.NO_QR_CODE_FOUND;
return n?{data:l.rawValue,cornerPoints:h._convertPoints(l.cornerPoints,k)}:l.rawValue}catch(l){throw`Scanner error: ${l.message||l}`;}}))()])}catch(r){if(!k||!g)throw r;let p=yield h.scanImage(a,{qrEngine:d,canvas:e,disallowCanvasResizing:f});return n?p:p.data}finally{w||h._postWorkerMessage(d,"close")}})}setGrayscaleWeights(a,b,d,e=!0){h._postWorkerMessage(this._qrEnginePromise,"grayscaleWeights",{red:a,green:b,blue:d,useIntegerApproximation:e})}setInversionMode(a){h._postWorkerMessage(this._qrEnginePromise,
"inversionMode",a)}static createQrEngine(a=h.WORKER_PATH){return c.asyncExecutePromiseGeneratorFunction(function*(){return"BarcodeDetector"in window&&BarcodeDetector.getSupportedFormats&&(yield BarcodeDetector.getSupportedFormats()).includes("qr_code")?new BarcodeDetector({formats:["qr_code"]}):new Worker(a)})}_onPlay(){this._scanRegion=this._calculateScanRegion(this.$video);this._scanFrame()}_onLoadedMetaData(){this._scanRegion=this._calculateScanRegion(this.$video)}_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}}static _convertPoints(a,b){if(!b)return a;let d=b.x||0,e=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 g of a)g.x=g.x*f+
d,g.y=g.y*b+e;return a}_scanFrame(){!this._active||this.$video.paused||this.$video.ended||requestAnimationFrame(()=>{const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){if(!(1>=a.$video.readyState)){try{var b=yield h.scanImage(a.$video,{scanRegion:a._scanRegion,qrEngine:a._qrEnginePromise,canvas:a.$canvas})}catch(d){if(!a._active)return;(d.message||d).includes("service unavailable")&&(a._qrEnginePromise=h.createQrEngine());a._onDecodeError(d)}b&&a._onDecode?a._onDecode(b):b&&a._legacyOnDecode&&
a._legacyOnDecode(b.data)}a._scanFrame()})})}_onDecodeError(a){a!==h.NO_QR_CODE_FOUND&&console.log(a)}_getCameraStream(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){if(!navigator.mediaDevices)throw"Camera not found.";let b=/^(environment|user)$/.test(a._preferredCamera)?"facingMode":"deviceId",d=[{width:{min:1024}},{width:{min:768}},{}],e=d.map(f=>Object.assign({},f,{[b]:{exact:a._preferredCamera}}));for(let f of[...e,...d])try{let g=yield navigator.mediaDevices.getUserMedia({video:f,
audio:!1}),k=a._getFacingMode(g)||(f.facingMode?a._preferredCamera:"environment"===a._preferredCamera?"user":"environment");return{stream:g,facingMode:k}}catch(g){}throw"Camera not found.";})}_restartVideoStream(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){let b=a._paused;(yield a.pause(!0))&&!b&&a._active&&(yield a.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,d,e=!1){d=d||document.createElement("canvas");let f=b&&b.x?b.x:0,g=b&&b.y?b.y:0,k=b&&b.width?b.width:a.videoWidth||a.width,n=b&&b.height?b.height:a.videoHeight||a.height;e||(e=b&&b.downScaledWidth?b.downScaledWidth:k,b=b&&b.downScaledHeight?b.downScaledHeight:n,d.width!==e&&(d.width=e),d.height!==b&&(d.height=
b));b=d.getContext("2d",{alpha:!1});b.imageSmoothingEnabled=!1;b.drawImage(a,f,g,k,n,0,0,d.width,d.height);return[d,b]}static _loadImage(a){return c.asyncExecutePromiseGeneratorFunction(function*(){if(a instanceof Image)return yield h._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 yield h._awaitImageLoad(b),b}finally{(a instanceof File||a instanceof Blob)&&URL.revokeObjectURL(b.src)}}else throw"Unsupported image type.";})}static _awaitImageLoad(a){return c.asyncExecutePromiseGeneratorFunction(function*(){a.complete&&0!==a.naturalWidth||(yield new Promise((b,d)=>{let e=f=>{a.removeEventListener("load",e);a.removeEventListener("error",
e);f instanceof ErrorEvent?d("Image load error"):b()};a.addEventListener("load",e);a.addEventListener("error",e)}))})}static _postWorkerMessage(a,b,d){return c.asyncExecutePromiseGeneratorFunction(function*(){let e=yield a;e instanceof Worker&&e.postMessage({type:b,data:d})})}}h.DEFAULT_CANVAS_SIZE=400;h.NO_QR_CODE_FOUND="No QR code found";h.WORKER_PATH="qr-scanner-worker.min.js";export default h
//# sourceMappingURL=qr-scanner.legacy.min.js.map
File diff suppressed because one or more lines are too long
+22 -20
View File
@@ -1,21 +1,23 @@
class e{constructor(a,b,c,d,f){this._preferredCamera="environment";this._legacyCanvasSize=e.DEFAULT_CANVAS_SIZE;this._destroyed=this._flashOn=this._paused=this._active=!1;this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;(c&&"object"!==typeof c||d||f)&&console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future");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._scanRegion=this._calculateScanRegion(a);this._onPlay=this._onPlay.bind(this);this._onLoadedMetaData=this._onLoadedMetaData.bind(this);this._onVisibilityChange=this._onVisibilityChange.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);requestAnimationFrame(()=>{let k=window.getComputedStyle(a);"none"===k.display&&(a.style.setProperty("display","block","important"),h=!0);"visible"!==k.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")});a.addEventListener("play",this._onPlay);a.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrEnginePromise=e.createQrEngine()}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);this._destroyed=!0;this._flashOn=!1;this.stop();e._postWorkerMessage(this._qrEnginePromise,
"close")}async start(){if(!(this._active&&!this._paused||this._destroyed||("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.$video.srcObject=a,await this.$video.play(),this._setVideoMirror(b),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();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 k;b&&("scanRegion"in b||"qrEngine"in b||"canvas"in b||"disallowCanvasResizing"in b||"alsoTryWithoutScanRegion"in b)?(k=b.scanRegion,c=b.qrEngine,d=b.canvas,f=b.disallowCanvasResizing||!1,h=b.alsoTryWithoutScanRegion||!1):(b||c||d||f||h)&&console.warn("You're using a deprecated api for scanImage which will be removed in the future.");b=!!c;try{let l,t;[c,l]=await Promise.all([c||e.createQrEngine(),e._loadImage(a)]);[d,
t]=e._drawToCanvas(l,k,d,f);if(c instanceof Worker){let g=c;b||g.postMessage({type:"inversionMode",data:"both"});return await new Promise((q,u)=>{let r,p,n;p=m=>{"qrResult"===m.data.type&&(g.removeEventListener("message",p),g.removeEventListener("error",n),clearTimeout(r),null!==m.data.data?q(m.data.data):u(e.NO_QR_CODE_FOUND))};n=m=>{g.removeEventListener("message",p);g.removeEventListener("error",n);clearTimeout(r);u("Scanner error: "+(m?m.message||m:"Unknown Error"))};g.addEventListener("message",
p);g.addEventListener("error",n);r=setTimeout(()=>n("timeout"),1E4);let v=t.getImageData(0,0,d.width,d.height);g.postMessage({type:"decode",data:v},[v.data.buffer])})}return await Promise.race([new Promise((g,q)=>window.setTimeout(()=>q("Scanner error: timeout"),1E4)),(async()=>{try{let [g]=await c.detect(d);if(!g)throw e.NO_QR_CODE_FOUND;return g.rawValue}catch(g){throw`Scanner error: ${g.message||g}`;}})()])}catch(l){if(!k||!h)throw l;return await e.scanImage(a,{qrEngine:c,canvas:d,disallowCanvasResizing:f})}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=e.WORKER_PATH){return"BarcodeDetector"in window&&BarcodeDetector.getSupportedFormats&&(await BarcodeDetector.getSupportedFormats()).includes("qr_code")?new BarcodeDetector({formats:["qr_code"]}):new Worker(a)}_onPlay(){this._scanRegion=
this._calculateScanRegion(this.$video);this._scanFrame()}_onLoadedMetaData(){this._scanRegion=this._calculateScanRegion(this.$video)}_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}}_scanFrame(){!this._active||this.$video.paused||
this.$video.ended||requestAnimationFrame(async()=>{if(!(1>=this.$video.readyState)){try{var a=await e.scanImage(this.$video,{scanRegion:this._scanRegion,qrEngine:this._qrEnginePromise,canvas:this.$canvas})}catch(b){if(!this._active)return;(b.message||b).includes("service unavailable")&&(this._qrEnginePromise=e.createQrEngine());this._onDecodeError(b)}a&&this._onDecode(a)}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,k=b&&b.width?
b.width:a.videoWidth||a.width,l=b&&b.height?b.height:a.videoHeight||a.height;d||(d=b&&b.downScaledWidth?b.downScaledWidth:k,b=b&&b.downScaledHeight?b.downScaledHeight:l,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,k,l,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){a=await a;a instanceof Worker&&a.postMessage({type:b,data:c})}}e.DEFAULT_CANVAS_SIZE=400;e.NO_QR_CODE_FOUND="No QR code found";e.WORKER_PATH="qr-scanner-worker.min.js";export default e
class e{constructor(a,b,c,d,f){this._legacyCanvasSize=e.DEFAULT_CANVAS_SIZE;this._preferredCamera="environment";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._scanRegion=this._calculateScanRegion(a);this._onPlay=this._onPlay.bind(this);this._onLoadedMetaData=this._onLoadedMetaData.bind(this);
this._onVisibilityChange=this._onVisibilityChange.bind(this);a.disablePictureInPicture=!0;a.playsInline=!0;a.muted=!0;let g=!1;a.hidden&&(a.hidden=!1,g=!0);document.body.contains(a)||(document.body.appendChild(a),g=!0);requestAnimationFrame(()=>{let k=window.getComputedStyle(a);"none"===k.display&&(a.style.setProperty("display","block","important"),g=!0);"visible"!==k.visibility&&(a.style.setProperty("visibility","visible","important"),g=!0);g&&(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")});a.addEventListener("play",this._onPlay);a.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrEnginePromise=e.createQrEngine()}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);
this._destroyed=!0;this._flashOn=!1;this.stop();e._postWorkerMessage(this._qrEnginePromise,"close")}async start(){if(!(this._active&&!this._paused||this._destroyed||("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();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,g=!1){let k,m=!1;b&&("scanRegion"in b||"qrEngine"in b||"canvas"in b||"disallowCanvasResizing"in b||"alsoTryWithoutScanRegion"in b||"returnDetailedScanResult"in b)?(k=b.scanRegion,c=b.qrEngine,d=b.canvas,f=b.disallowCanvasResizing||!1,g=b.alsoTryWithoutScanRegion||!1,m=!0):b||c||d||f||g?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 q,n;[c,q]=await Promise.all([c||e.createQrEngine(),e._loadImage(a)]);[d,n]=e._drawToCanvas(q,k,d,f);if(c instanceof Worker){let h=c;b||h.postMessage({type:"inversionMode",data:"both"});return await new Promise((t,v)=>{let u,r,p;r=l=>{"qrResult"===l.data.type&&(h.removeEventListener("message",r),h.removeEventListener("error",
p),clearTimeout(u),null!==l.data.data?t(m?{data:l.data.data,cornerPoints:e._convertPoints(l.data.cornerPoints,k)}:l.data.data):v(e.NO_QR_CODE_FOUND))};p=l=>{h.removeEventListener("message",r);h.removeEventListener("error",p);clearTimeout(u);v("Scanner error: "+(l?l.message||l:"Unknown Error"))};h.addEventListener("message",r);h.addEventListener("error",p);u=setTimeout(()=>p("timeout"),1E4);let w=n.getImageData(0,0,d.width,d.height);h.postMessage({type:"decode",data:w},[w.data.buffer])})}return await Promise.race([new Promise((h,
t)=>window.setTimeout(()=>t("Scanner error: timeout"),1E4)),(async()=>{try{let [h]=await c.detect(d);if(!h)throw e.NO_QR_CODE_FOUND;return m?{data:h.rawValue,cornerPoints:e._convertPoints(h.cornerPoints,k)}:h.rawValue}catch(h){throw`Scanner error: ${h.message||h}`;}})()])}catch(q){if(!k||!g)throw q;let n=await e.scanImage(a,{qrEngine:c,canvas:d,disallowCanvasResizing:f});return m?n:n.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=e.WORKER_PATH){return"BarcodeDetector"in window&&BarcodeDetector.getSupportedFormats&&(await BarcodeDetector.getSupportedFormats()).includes("qr_code")?new BarcodeDetector({formats:["qr_code"]}):new Worker(a)}_onPlay(){this._scanRegion=this._calculateScanRegion(this.$video);this._scanFrame()}_onLoadedMetaData(){this._scanRegion=
this._calculateScanRegion(this.$video)}_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}}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 g of a)g.x=g.x*f+c,g.y=g.y*b+d;return a}_scanFrame(){!this._active||this.$video.paused||this.$video.ended||requestAnimationFrame(async()=>{if(!(1>=this.$video.readyState)){try{var a=await e.scanImage(this.$video,{scanRegion:this._scanRegion,qrEngine:this._qrEnginePromise,canvas:this.$canvas})}catch(b){if(!this._active)return;(b.message||b).includes("service unavailable")&&(this._qrEnginePromise=e.createQrEngine());this._onDecodeError(b)}a&&
this._onDecode?this._onDecode(a):a&&this._legacyOnDecode&&this._legacyOnDecode(a.data)}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}),g=this._getFacingMode(f)||(d.facingMode?this._preferredCamera:"environment"===this._preferredCamera?"user":"environment");return{stream:f,facingMode:g}}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,g=b&&b.y?b.y:0,k=b&&b.width?b.width:a.videoWidth||a.width,m=b&&b.height?b.height:a.videoHeight||a.height;d||(d=b&&b.downScaledWidth?b.downScaledWidth:k,b=b&&b.downScaledHeight?b.downScaledHeight:m,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,g,k,m,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){a=await a;a instanceof
Worker&&a.postMessage({type:b,data:c})}}e.DEFAULT_CANVAS_SIZE=400;e.NO_QR_CODE_FOUND="No QR code found";e.WORKER_PATH="qr-scanner-worker.min.js";export default e
//# sourceMappingURL=qr-scanner.min.js.map
File diff suppressed because one or more lines are too long
+22 -21
View File
@@ -1,22 +1,23 @@
'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._preferredCamera="environment";this._legacyCanvasSize=e.DEFAULT_CANVAS_SIZE;this._destroyed=this._flashOn=this._paused=this._active=!1;this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;(c&&"object"!==
typeof c||d||f)&&console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future");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._scanRegion=
this._calculateScanRegion(a);this._onPlay=this._onPlay.bind(this);this._onLoadedMetaData=this._onLoadedMetaData.bind(this);this._onVisibilityChange=this._onVisibilityChange.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);requestAnimationFrame(()=>{let k=window.getComputedStyle(a);"none"===k.display&&(a.style.setProperty("display","block","important"),h=!0);"visible"!==k.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")});a.addEventListener("play",this._onPlay);a.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrEnginePromise=e.createQrEngine()}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);this._destroyed=!0;this._flashOn=!1;this.stop();e._postWorkerMessage(this._qrEnginePromise,"close")}async start(){if(!(this._active&&!this._paused||this._destroyed||("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.$video.srcObject=a,await this.$video.play(),this._setVideoMirror(b),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();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 k;b&&("scanRegion"in b||"qrEngine"in b||"canvas"in b||"disallowCanvasResizing"in b||"alsoTryWithoutScanRegion"in b)?(k=b.scanRegion,c=b.qrEngine,d=b.canvas,f=b.disallowCanvasResizing||!1,h=b.alsoTryWithoutScanRegion||!1):(b||c||d||f||h)&&console.warn("You're using a deprecated api for scanImage which will be removed in the future.");
b=!!c;try{let l,t;[c,l]=await Promise.all([c||e.createQrEngine(),e._loadImage(a)]);[d,t]=e._drawToCanvas(l,k,d,f);if(c instanceof Worker){let g=c;b||g.postMessage({type:"inversionMode",data:"both"});return await new Promise((q,u)=>{let r,p,n;p=m=>{"qrResult"===m.data.type&&(g.removeEventListener("message",p),g.removeEventListener("error",n),clearTimeout(r),null!==m.data.data?q(m.data.data):u(e.NO_QR_CODE_FOUND))};n=m=>{g.removeEventListener("message",p);g.removeEventListener("error",n);clearTimeout(r);
u("Scanner error: "+(m?m.message||m:"Unknown Error"))};g.addEventListener("message",p);g.addEventListener("error",n);r=setTimeout(()=>n("timeout"),1E4);let v=t.getImageData(0,0,d.width,d.height);g.postMessage({type:"decode",data:v},[v.data.buffer])})}return await Promise.race([new Promise((g,q)=>window.setTimeout(()=>q("Scanner error: timeout"),1E4)),(async()=>{try{let [g]=await c.detect(d);if(!g)throw e.NO_QR_CODE_FOUND;return g.rawValue}catch(g){throw`Scanner error: ${g.message||g}`;}})()])}catch(l){if(!k||
!h)throw l;return await e.scanImage(a,{qrEngine:c,canvas:d,disallowCanvasResizing:f})}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=e.WORKER_PATH){return"BarcodeDetector"in window&&BarcodeDetector.getSupportedFormats&&(await BarcodeDetector.getSupportedFormats()).includes("qr_code")?
new BarcodeDetector({formats:["qr_code"]}):new Worker(a)}_onPlay(){this._scanRegion=this._calculateScanRegion(this.$video);this._scanFrame()}_onLoadedMetaData(){this._scanRegion=this._calculateScanRegion(this.$video)}_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}}_scanFrame(){!this._active||this.$video.paused||this.$video.ended||requestAnimationFrame(async()=>{if(!(1>=this.$video.readyState)){try{var a=await e.scanImage(this.$video,{scanRegion:this._scanRegion,qrEngine:this._qrEnginePromise,canvas:this.$canvas})}catch(b){if(!this._active)return;(b.message||b).includes("service unavailable")&&(this._qrEnginePromise=e.createQrEngine());this._onDecodeError(b)}a&&this._onDecode(a)}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,k=b&&b.width?b.width:a.videoWidth||a.width,l=b&&b.height?b.height:a.videoHeight||a.height;d||(d=b&&b.downScaledWidth?b.downScaledWidth:k,b=b&&b.downScaledHeight?b.downScaledHeight:l,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,k,l,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){a=await a;a instanceof Worker&&a.postMessage({type:b,data:c})}}e.DEFAULT_CANVAS_SIZE=400;e.NO_QR_CODE_FOUND="No QR code found";
e.WORKER_PATH="qr-scanner-worker.min.js";return e})
'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._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._scanRegion=this._calculateScanRegion(a);this._onPlay=this._onPlay.bind(this);this._onLoadedMetaData=this._onLoadedMetaData.bind(this);this._onVisibilityChange=this._onVisibilityChange.bind(this);a.disablePictureInPicture=!0;a.playsInline=!0;a.muted=!0;let g=!1;a.hidden&&(a.hidden=!1,g=!0);document.body.contains(a)||
(document.body.appendChild(a),g=!0);requestAnimationFrame(()=>{let k=window.getComputedStyle(a);"none"===k.display&&(a.style.setProperty("display","block","important"),g=!0);"visible"!==k.visibility&&(a.style.setProperty("visibility","visible","important"),g=!0);g&&(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")});a.addEventListener("play",this._onPlay);a.addEventListener("loadedmetadata",
this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrEnginePromise=e.createQrEngine()}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);this._destroyed=!0;this._flashOn=!1;this.stop();e._postWorkerMessage(this._qrEnginePromise,"close")}async start(){if(!(this._active&&!this._paused||this._destroyed||("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();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,g=!1){let k,m=!1;b&&("scanRegion"in b||
"qrEngine"in b||"canvas"in b||"disallowCanvasResizing"in b||"alsoTryWithoutScanRegion"in b||"returnDetailedScanResult"in b)?(k=b.scanRegion,c=b.qrEngine,d=b.canvas,f=b.disallowCanvasResizing||!1,g=b.alsoTryWithoutScanRegion||!1,m=!0):b||c||d||f||g?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 q,n;[c,q]=await Promise.all([c||e.createQrEngine(),e._loadImage(a)]);[d,n]=e._drawToCanvas(q,k,d,f);if(c instanceof Worker){let h=c;b||h.postMessage({type:"inversionMode",data:"both"});return await new Promise((t,v)=>{let u,r,p;r=l=>{"qrResult"===l.data.type&&(h.removeEventListener("message",r),h.removeEventListener("error",p),clearTimeout(u),null!==l.data.data?t(m?{data:l.data.data,cornerPoints:e._convertPoints(l.data.cornerPoints,k)}:l.data.data):v(e.NO_QR_CODE_FOUND))};p=l=>{h.removeEventListener("message",
r);h.removeEventListener("error",p);clearTimeout(u);v("Scanner error: "+(l?l.message||l:"Unknown Error"))};h.addEventListener("message",r);h.addEventListener("error",p);u=setTimeout(()=>p("timeout"),1E4);let w=n.getImageData(0,0,d.width,d.height);h.postMessage({type:"decode",data:w},[w.data.buffer])})}return await Promise.race([new Promise((h,t)=>window.setTimeout(()=>t("Scanner error: timeout"),1E4)),(async()=>{try{let [h]=await c.detect(d);if(!h)throw e.NO_QR_CODE_FOUND;return m?{data:h.rawValue,
cornerPoints:e._convertPoints(h.cornerPoints,k)}:h.rawValue}catch(h){throw`Scanner error: ${h.message||h}`;}})()])}catch(q){if(!k||!g)throw q;let n=await e.scanImage(a,{qrEngine:c,canvas:d,disallowCanvasResizing:f});return m?n:n.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=e.WORKER_PATH){return"BarcodeDetector"in window&&BarcodeDetector.getSupportedFormats&&(await BarcodeDetector.getSupportedFormats()).includes("qr_code")?new BarcodeDetector({formats:["qr_code"]}):new Worker(a)}_onPlay(){this._scanRegion=this._calculateScanRegion(this.$video);this._scanFrame()}_onLoadedMetaData(){this._scanRegion=this._calculateScanRegion(this.$video)}_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}}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 g of a)g.x=g.x*f+c,g.y=g.y*b+d;return a}_scanFrame(){!this._active||this.$video.paused||
this.$video.ended||requestAnimationFrame(async()=>{if(!(1>=this.$video.readyState)){try{var a=await e.scanImage(this.$video,{scanRegion:this._scanRegion,qrEngine:this._qrEnginePromise,canvas:this.$canvas})}catch(b){if(!this._active)return;(b.message||b).includes("service unavailable")&&(this._qrEnginePromise=e.createQrEngine());this._onDecodeError(b)}a&&this._onDecode?this._onDecode(a):a&&this._legacyOnDecode&&this._legacyOnDecode(a.data)}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}),g=this._getFacingMode(f)||(d.facingMode?this._preferredCamera:"environment"===this._preferredCamera?"user":"environment");
return{stream:f,facingMode:g}}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,g=b&&b.y?b.y:0,k=b&&b.width?b.width:a.videoWidth||a.width,m=b&&b.height?b.height:a.videoHeight||a.height;d||(d=b&&b.downScaledWidth?b.downScaledWidth:k,b=b&&b.downScaledHeight?b.downScaledHeight:m,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,g,k,m,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){a=await a;a instanceof Worker&&a.postMessage({type:b,data:c})}}e.DEFAULT_CANVAS_SIZE=400;e.NO_QR_CODE_FOUND="No QR code found";e.WORKER_PATH="qr-scanner-worker.min.js";return e})
//# sourceMappingURL=qr-scanner.umd.min.js.map
File diff suppressed because one or more lines are too long
+98 -25
View File
@@ -48,10 +48,11 @@ export default class QrScanner {
$video: HTMLVideoElement;
$canvas: HTMLCanvasElement;
private readonly _onDecode: (result: string) => void;
private readonly _onDecode?: (result: QrScanner.ScanResult) => void;
private readonly _legacyOnDecode?: (result: string) => void;
private readonly _legacyCanvasSize: number = QrScanner.DEFAULT_CANVAS_SIZE;
private _preferredCamera: QrScanner.FacingMode | QrScanner.DeviceId = 'environment';
private _scanRegion: QrScanner.ScanRegion;
private _legacyCanvasSize: number = QrScanner.DEFAULT_CANVAS_SIZE;
private _qrEnginePromise: Promise<Worker | BarcodeDetector>
private _active: boolean = false;
private _paused: boolean = false;
@@ -60,11 +61,13 @@ export default class QrScanner {
constructor(
video: HTMLVideoElement,
onDecode: (result: string) => void,
options?: {
onDecode: (result: QrScanner.ScanResult) => void,
options: {
onDecodeError?: (error: Error | string) => void,
calculateScanRegion?: (video: HTMLVideoElement) => QrScanner.ScanRegion,
preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,
/** just a temporary flag until we switch entirely to the new api */
returnDetailedScanResult?: true,
},
);
/** @deprecated */
@@ -87,24 +90,37 @@ export default class QrScanner {
constructor(video: HTMLVideoElement, onDecode: (result: string) => void, canvasSize?: number);
constructor(
video: HTMLVideoElement,
onDecode: (result: string) => void,
onDecode: ((result: QrScanner.ScanResult) => void) | ((result: string) => void),
canvasSizeOrOnDecodeErrorOrOptions?: number | ((error: Error | string) => void) | {
onDecodeError?: (error: Error | string) => void,
calculateScanRegion?: (video: HTMLVideoElement) => QrScanner.ScanRegion,
preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,
/** just a temporary flag until we switch entirely to the new api */
returnDetailedScanResult?: true,
},
canvasSizeOrCalculateScanRegion?: number | ((video: HTMLVideoElement) => QrScanner.ScanRegion),
preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,
) {
this.$video = video;
this.$canvas = document.createElement('canvas');
this._onDecode = onDecode;
if ((canvasSizeOrOnDecodeErrorOrOptions && typeof canvasSizeOrOnDecodeErrorOrOptions !== 'object')
|| (canvasSizeOrCalculateScanRegion || preferredCamera)) {
// New constructor signature has the optional options object as last argument not followed by anything else.
console.warn('You\'re using a deprecated version of the QrScanner constructor which will be removed in '
+ 'the future');
if (canvasSizeOrOnDecodeErrorOrOptions && typeof canvasSizeOrOnDecodeErrorOrOptions === 'object') {
// we got an options object using the new api
this._onDecode = onDecode as QrScanner['_onDecode'];
} else {
if (canvasSizeOrOnDecodeErrorOrOptions || canvasSizeOrCalculateScanRegion || preferredCamera) {
console.warn('You\'re using a deprecated version of the QrScanner constructor which will be removed in '
+ 'the future');
} else {
// Only video and onDecode were specified and we can't distinguish between new or old api usage. For
// backwards compatibility we have to assume the old api for now. The options object is marked as non-
// optional in the parameter list above to make clear that ScanResult instead of string is only passed
// if an options object was provided. However, in the future once legacy support is removed, the options
// object should become optional.
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 = onDecode as QrScanner['_legacyOnDecode'];
}
const options = typeof canvasSizeOrOnDecodeErrorOrOptions === 'object'
@@ -272,9 +288,9 @@ export default class QrScanner {
QrScanner._stopVideoStream(stream);
return;
}
this._setVideoMirror(facingMode);
this.$video.srcObject = stream;
await this.$video.play();
this._setVideoMirror(facingMode);
// Restart the flash if it was previously on
if (this._flashOn) {
@@ -327,14 +343,16 @@ export default class QrScanner {
static async scanImage(
imageOrFileOrBlobOrUrl: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap
| SVGImageElement | File | Blob | URL | String,
options?: {
options: {
scanRegion?: QrScanner.ScanRegion | null,
qrEngine?: Worker | BarcodeDetector | Promise<Worker | BarcodeDetector> | null,
canvas?: HTMLCanvasElement | null,
disallowCanvasResizing?: boolean,
alsoTryWithoutScanRegion?: boolean,
/** just a temporary flag until we switch entirely to the new api */
returnDetailedScanResult?: true,
},
): Promise<string>;
): Promise<QrScanner.ScanResult>;
/** @deprecated */
static async scanImage(
imageOrFileOrBlobOrUrl: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap
@@ -354,28 +372,41 @@ export default class QrScanner {
canvas?: HTMLCanvasElement | null,
disallowCanvasResizing?: boolean,
alsoTryWithoutScanRegion?: boolean,
/** just a temporary flag until we switch entirely to the new api */
returnDetailedScanResult?: true,
} | null,
qrEngine?: Worker | BarcodeDetector | Promise<Worker | BarcodeDetector> | null,
canvas?: HTMLCanvasElement | null,
disallowCanvasResizing: boolean = false,
alsoTryWithoutScanRegion: boolean = false,
): Promise<string> {
): Promise<string | QrScanner.ScanResult> {
let scanRegion: QrScanner.ScanRegion | null | undefined;
let returnDetailedScanResult = false;
if (scanRegionOrOptions && (
'scanRegion' in scanRegionOrOptions
|| 'qrEngine' in scanRegionOrOptions
|| 'canvas' in scanRegionOrOptions
|| 'disallowCanvasResizing' in scanRegionOrOptions
|| 'alsoTryWithoutScanRegion' in scanRegionOrOptions
|| 'returnDetailedScanResult' in scanRegionOrOptions
)) {
// we got an options object
// we got an options object using the new api
scanRegion = scanRegionOrOptions.scanRegion;
qrEngine = scanRegionOrOptions.qrEngine;
canvas = scanRegionOrOptions.canvas;
disallowCanvasResizing = scanRegionOrOptions.disallowCanvasResizing || false;
alsoTryWithoutScanRegion = scanRegionOrOptions.alsoTryWithoutScanRegion || false;
returnDetailedScanResult = true;
} else if (scanRegionOrOptions || qrEngine || canvas || disallowCanvasResizing || alsoTryWithoutScanRegion) {
console.warn('You\'re using a deprecated api for scanImage which will be removed in the future.');
} else {
// Only imageOrFileOrBlobOrUrl was specified and we can't distinguish between new or old api usage. For
// backwards compatibility we have to assume the old api for now. The options object is marked as non-
// optional in the parameter list above to make clear that ScanResult instead of string is only returned if
// an options object was provided. However, in the future once legacy support is removed, the options object
// should become optional.
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.');
}
const gotExternalEngine = !!qrEngine;
@@ -408,7 +439,10 @@ export default class QrScanner {
qrEngineWorker.removeEventListener('error', onError);
clearTimeout(timeout);
if (event.data.data !== null) {
resolve(event.data.data);
resolve(returnDetailedScanResult ? {
data: event.data.data,
cornerPoints: QrScanner._convertPoints(event.data.cornerPoints, scanRegion),
} : event.data.data);
} else {
reject(QrScanner.NO_QR_CODE_FOUND);
}
@@ -431,15 +465,18 @@ export default class QrScanner {
});
} else {
return await Promise.race([
new Promise<string>((resolve, reject) => window.setTimeout(
new Promise<QrScanner.ScanResult | string>((resolve, reject) => window.setTimeout(
() => reject('Scanner error: timeout'),
10000,
)),
(async (): Promise<string> => {
(async (): Promise<QrScanner.ScanResult | string> => {
try {
const [scanResult] = await qrEngine.detect(canvas!);
if (!scanResult) throw QrScanner.NO_QR_CODE_FOUND;
return scanResult.rawValue;
return returnDetailedScanResult ? {
data: scanResult.rawValue,
cornerPoints: QrScanner._convertPoints(scanResult.cornerPoints, scanRegion),
} : scanResult.rawValue;
} catch (e) {
throw `Scanner error: ${(e as Error).message || e}`;
}
@@ -448,7 +485,11 @@ export default class QrScanner {
}
} catch (e) {
if (!scanRegion || !alsoTryWithoutScanRegion) throw e;
return await QrScanner.scanImage(imageOrFileOrBlobOrUrl, { qrEngine, canvas, disallowCanvasResizing });
const detailedScanResult = await QrScanner.scanImage(
imageOrFileOrBlobOrUrl,
{ qrEngine, canvas, disallowCanvasResizing },
);
return returnDetailedScanResult ? detailedScanResult : detailedScanResult.data;
} finally {
if (!gotExternalEngine) {
QrScanner._postWorkerMessage(qrEngine!, 'close');
@@ -510,6 +551,26 @@ export default class QrScanner {
};
}
private static _convertPoints(
points: QrScanner.Point[],
scanRegion?: QrScanner.ScanRegion | null,
): QrScanner.Point[] {
if (!scanRegion) return points;
const offsetX = scanRegion.x || 0;
const offsetY = scanRegion.y || 0;
const scaleFactorX = scanRegion.width && scanRegion.downScaledWidth
? scanRegion.width / scanRegion.downScaledWidth
: 1;
const scaleFactorY = scanRegion.height && scanRegion.downScaledHeight
? scanRegion.height / scanRegion.downScaledHeight
: 1;
for (const point of points) {
point.x = point.x * scaleFactorX + offsetX;
point.y = point.y * scaleFactorY + offsetY;
}
return points;
}
private _scanFrame(): void {
if (!this._active || this.$video.paused || this.$video.ended) return;
// using requestAnimationFrame to avoid scanning if tab is in background
@@ -523,7 +584,7 @@ export default class QrScanner {
return;
}
let result: string | undefined;
let result: QrScanner.ScanResult | undefined;
try {
result = await QrScanner.scanImage(this.$video, {
scanRegion: this._scanRegion,
@@ -540,8 +601,10 @@ export default class QrScanner {
this._onDecodeError(error as Error | string);
}
if (result) {
if (result && this._onDecode) {
this._onDecode(result);
} else if (result && this._legacyOnDecode) {
this._legacyOnDecode(result.data);
}
this._scanFrame();
@@ -749,12 +812,22 @@ declare namespace QrScanner {
}
export type InversionMode = 'original' | 'invert' | 'both';
export interface Point {
x: number;
y: number;
}
export interface ScanResult {
data: string;
// In clockwise order, starting at top left, but this might not be guaranteed in the future.
cornerPoints: QrScanner.Point[];
}
}
// simplified from https://wicg.github.io/shape-detection-api/#barcode-detection-api
declare class BarcodeDetector {
constructor(options?: { formats: string[] });
static getSupportedFormats(): Promise<string[]>;
detect(image: ImageBitmapSource): Promise<Array<{ rawValue: string }>>;
detect(image: ImageBitmapSource): Promise<Array<{ rawValue: string, cornerPoints: QrScanner.Point[] }>>;
}
+16 -1
View File
@@ -46,9 +46,24 @@ function decode(data: { data: Uint8ClampedArray, width: number, height: number }
inversionAttempts: inversionAttempts,
greyScaleWeights: grayscaleWeights,
});
if (!result) {
(self as unknown as Worker).postMessage({
type: 'qrResult',
data: null,
});
return;
}
(self as unknown as Worker).postMessage({
type: 'qrResult',
data: result? result.data : null,
data: result.data,
// equivalent to cornerPoints of native BarcodeDetector
cornerPoints: [
result.location.topLeftCorner,
result.location.topRightCorner,
result.location.bottomRightCorner,
result.location.bottomLeftCorner,
],
});
}
+20 -5
View File
@@ -7,19 +7,22 @@ export default class QrScanner {
static listCameras(requestLabels?: boolean): Promise<Array<QrScanner.Camera>>;
$video: HTMLVideoElement;
$canvas: HTMLCanvasElement;
private readonly _onDecode;
private readonly _onDecode?;
private readonly _legacyOnDecode?;
private readonly _legacyCanvasSize;
private _preferredCamera;
private _scanRegion;
private _legacyCanvasSize;
private _qrEnginePromise;
private _active;
private _paused;
private _flashOn;
private _destroyed;
constructor(video: HTMLVideoElement, onDecode: (result: string) => void, options?: {
constructor(video: HTMLVideoElement, onDecode: (result: QrScanner.ScanResult) => void, options: {
onDecodeError?: (error: Error | string) => void;
calculateScanRegion?: (video: HTMLVideoElement) => QrScanner.ScanRegion;
preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId;
/** just a temporary flag until we switch entirely to the new api */
returnDetailedScanResult?: true;
});
/** @deprecated */
constructor(video: HTMLVideoElement, onDecode: (result: string) => void, onDecodeError?: (error: Error | string) => void, calculateScanRegion?: (video: HTMLVideoElement) => QrScanner.ScanRegion, preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId);
@@ -37,13 +40,15 @@ export default class QrScanner {
stop(): void;
pause(stopStreamImmediately?: boolean): Promise<boolean>;
setCamera(facingModeOrDeviceId: QrScanner.FacingMode | QrScanner.DeviceId): Promise<void>;
static scanImage(imageOrFileOrBlobOrUrl: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap | SVGImageElement | File | Blob | URL | String, options?: {
static scanImage(imageOrFileOrBlobOrUrl: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap | SVGImageElement | File | Blob | URL | String, options: {
scanRegion?: QrScanner.ScanRegion | null;
qrEngine?: Worker | BarcodeDetector | Promise<Worker | BarcodeDetector> | null;
canvas?: HTMLCanvasElement | null;
disallowCanvasResizing?: boolean;
alsoTryWithoutScanRegion?: boolean;
}): Promise<string>;
/** just a temporary flag until we switch entirely to the new api */
returnDetailedScanResult?: true;
}): Promise<QrScanner.ScanResult>;
/** @deprecated */
static scanImage(imageOrFileOrBlobOrUrl: HTMLImageElement | HTMLVideoElement | HTMLCanvasElement | OffscreenCanvas | ImageBitmap | SVGImageElement | File | Blob | URL | String, scanRegion?: QrScanner.ScanRegion | null, qrEngine?: Worker | BarcodeDetector | Promise<Worker | BarcodeDetector> | null, canvas?: HTMLCanvasElement | null, disallowCanvasResizing?: boolean, alsoTryWithoutScanRegion?: boolean): Promise<string>;
setGrayscaleWeights(red: number, green: number, blue: number, useIntegerApproximation?: boolean): void;
@@ -53,6 +58,7 @@ export default class QrScanner {
private _onLoadedMetaData;
private _onVisibilityChange;
private _calculateScanRegion;
private static _convertPoints;
private _scanFrame;
private _onDecodeError;
private _getCameraStream;
@@ -81,6 +87,14 @@ declare namespace QrScanner {
label: string;
}
type InversionMode = 'original' | 'invert' | 'both';
interface Point {
x: number;
y: number;
}
interface ScanResult {
data: string;
cornerPoints: QrScanner.Point[];
}
}
declare class BarcodeDetector {
constructor(options?: {
@@ -89,6 +103,7 @@ declare class BarcodeDetector {
static getSupportedFormats(): Promise<string[]>;
detect(image: ImageBitmapSource): Promise<Array<{
rawValue: string;
cornerPoints: QrScanner.Point[];
}>>;
}
export {};