Support highlighting the scan region

Closes #86
This commit is contained in:
Daniel
2022-01-26 16:43:25 +01:00
parent 57b2a52734
commit dea704924f
10 changed files with 143 additions and 69 deletions
+1
View File
@@ -151,6 +151,7 @@ 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. |
| `highlightScanRegion` | Set this option for rendering an outline around the the scan region on the video stream. This creates an absolutely positioned `div` that covers the scan region. This overlay can be accessed via `qrScanner.$scanRegionHighlight` and should be placed in the DOM as a sibling of `videoElem`. It can be custom styled via CSS, e.g. by setting an outline, border, background color, etc. For the overlay to be positioned correctly, the video's parent needs to match the video's size and be position `relative`, `absolute`, `fixed` or `sticky`. |
| `returnDetailedScanResult` | Enforce reporting detailed scan results, see below. |
To use the default value for an option, omit it or supply `undefined`.
+1
View File
@@ -128,6 +128,7 @@
camQrResult.textContent = error;
camQrResult.style.color = 'inherit';
},
highlightScanRegion: true,
});
const updateFlashAvailability = () => {
+22 -18
View File
@@ -18,26 +18,30 @@ c.generator.Engine_.prototype.nextStep_=function(){for(;this.context_.nextAddres
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 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"),
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;b.highlightScanRegion&&(this.$scanRegionHighlight=document.createElement("div"),this.$scanRegionHighlight.classList.add("scan-region-highlight"),
this.$scanRegionHighlight.style.position="absolute",this.$scanRegionHighlight.style.display="none",this.$scanRegionHighlight.style.outline="rgba(255, 255, 255, .3) solid 7px");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);let k=a.parentElement;requestAnimationFrame(()=>{let m=window.getComputedStyle(a),p=window.getComputedStyle(k);"none"===m.display&&(a.style.setProperty("display","block","important"),g=!0);"visible"!==m.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",delete this.$scanRegionHighlight);if(this.$scanRegionHighlight){["relative",
"absolute","fixed","sticky"].includes(p.position)||(console.warn("QrScanner has overwritten the video parent's position for correct display of the scan region highlight. To avoid this, set the position to relative, absolute, fixed or sticky."),k.style.setProperty("position","absolute","important"));if(p.width!==m.width||p.height!==m.height)console.warn("QrScanner has overwritten the video parent's width and height for correct display of the scan region highlight. To avoid this, make the parent as big as the video or the video as big as the parent."),
k.style.setProperty("width","max-content","important"),k.style.setProperty("height","max-content","important");this._updateScanRegionHighlight();k.appendChild(this.$scanRegionHighlight)}});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
!1){const b=this;return c.asyncExecutePromiseGeneratorFunction(function*(){b._paused=!0;if(!b._active)return!0;b.$video.pause();b.$scanRegionHighlight&&(b.$scanRegionHighlight.style.display="none");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,m=!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,m=!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 p=!!d;try{let t,q;[d,t]=yield Promise.all([d||h.createQrEngine(),h._loadImage(a)]);[e,q]=h._drawToCanvas(t,k,e,f);if(d instanceof Worker){let l=d;p||l.postMessage({type:"inversionMode",data:"both"});return yield new Promise((v,x)=>{let w,u,r;u=n=>{"qrResult"===n.data.type&&(l.removeEventListener("message",u),l.removeEventListener("error",
r),clearTimeout(w),null!==n.data.data?v(m?{data:n.data.data,cornerPoints:h._convertPoints(n.data.cornerPoints,k)}:n.data.data):x(h.NO_QR_CODE_FOUND))};r=n=>{l.removeEventListener("message",u);l.removeEventListener("error",r);clearTimeout(w);x("Scanner error: "+(n?n.message||n:"Unknown Error"))};l.addEventListener("message",u);l.addEventListener("error",r);w=setTimeout(()=>r("timeout"),1E4);let y=q.getImageData(0,0,e.width,e.height);l.postMessage({type:"decode",data:y},[y.data.buffer])})}return yield Promise.race([new Promise((l,
v)=>window.setTimeout(()=>v("Scanner error: timeout"),1E4)),(()=>c.asyncExecutePromiseGeneratorFunction(function*(){try{let [l]=yield d.detect(e);if(!l)throw h.NO_QR_CODE_FOUND;return m?{data:l.rawValue,cornerPoints:h._convertPoints(l.cornerPoints,k)}:l.rawValue}catch(l){throw`Scanner error: ${l.message||l}`;}}))()])}catch(t){if(!k||!g)throw t;let q=yield h.scanImage(a,{qrEngine:d,canvas:e,disallowCanvasResizing:f});return m?q:q.data}finally{p||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._updateScanRegionHighlight();this.$scanRegionHighlight&&(this.$scanRegionHighlight.style.display="");this._scanFrame()}_onLoadedMetaData(){this._scanRegion=this._calculateScanRegion(this.$video);this._updateScanRegionHighlight()}_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}}_updateScanRegionHighlight(){if(this.$scanRegionHighlight){var a=this.$video,b=a.videoWidth,d=a.videoHeight;this.$scanRegionHighlight.style.width=`${(this._scanRegion.width||b)/b*100}%`;this.$scanRegionHighlight.style.height=`${(this._scanRegion.height||d)/d*100}%`;this.$scanRegionHighlight.style.top=`${(this._scanRegion.y||0)/d*100}%`;a=/scaleX\(-1\)/.test(a.style.transform);this.$scanRegionHighlight.style[a?
"right":"left"]=`${(this._scanRegion.x||0)/b*100}%`;this.$scanRegionHighlight.style[a?"left":"right"]="unset"}}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,m=b&&b.height?b.height:a.videoHeight||a.height;e||(e=b&&b.downScaledWidth?b.downScaledWidth:k,b=b&&b.downScaledHeight?b.downScaledHeight:m,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,m,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
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"qr-scanner.legacy.min.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
{"version":3,"file":"qr-scanner.legacy.min.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
+24 -21
View File
@@ -1,23 +1,26 @@
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
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;b.highlightScanRegion&&(this.$scanRegionHighlight=document.createElement("div"),this.$scanRegionHighlight.classList.add("scan-region-highlight"),
this.$scanRegionHighlight.style.position="absolute",this.$scanRegionHighlight.style.display="none",this.$scanRegionHighlight.style.outline="rgba(255, 255, 255, .3) solid 7px");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);let k=a.parentElement;requestAnimationFrame(()=>{let l=window.getComputedStyle(a),n=window.getComputedStyle(k);"none"===l.display&&(a.style.setProperty("display","block","important"),g=!0);"visible"!==l.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",delete this.$scanRegionHighlight);if(this.$scanRegionHighlight){["relative",
"absolute","fixed","sticky"].includes(n.position)||(console.warn("QrScanner has overwritten the video parent's position for correct display of the scan region highlight. To avoid this, set the position to relative, absolute, fixed or sticky."),k.style.setProperty("position","absolute","important"));if(n.width!==l.width||n.height!==l.height)console.warn("QrScanner has overwritten the video parent's width and height for correct display of the scan region highlight. To avoid this, make the parent as big as the video or the video as big as the parent."),
k.style.setProperty("width","max-content","important"),k.style.setProperty("height","max-content","important");this._updateScanRegionHighlight();k.appendChild(this.$scanRegionHighlight)}});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();this.$scanRegionHighlight&&(this.$scanRegionHighlight.style.display="none");let b=()=>{this.$video.srcObject instanceof MediaStream&&(e._stopVideoStream(this.$video.srcObject),
this.$video.srcObject=null)};if(a)return b(),!0;await new Promise(c=>setTimeout(c,300));if(!this._paused)return!1;b();return!0}async setCamera(a){a!==this._preferredCamera&&(this._preferredCamera=a,await this._restartVideoStream())}static async scanImage(a,b,c,d,f=!1,g=!1){let k,l=!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,l=!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 n,p;[c,n]=await Promise.all([c||e.createQrEngine(),e._loadImage(a)]);[d,p]=e._drawToCanvas(n,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,q;r=m=>{"qrResult"===m.data.type&&(h.removeEventListener("message",r),h.removeEventListener("error",q),clearTimeout(u),null!==m.data.data?t(l?{data:m.data.data,cornerPoints:e._convertPoints(m.data.cornerPoints,k)}:m.data.data):v(e.NO_QR_CODE_FOUND))};q=m=>{h.removeEventListener("message",r);h.removeEventListener("error",q);clearTimeout(u);v("Scanner error: "+(m?m.message||m:"Unknown Error"))};h.addEventListener("message",r);h.addEventListener("error",
q);u=setTimeout(()=>q("timeout"),1E4);let w=p.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 l?{data:h.rawValue,cornerPoints:e._convertPoints(h.cornerPoints,k)}:h.rawValue}catch(h){throw`Scanner error: ${h.message||h}`;}})()])}catch(n){if(!k||!g)throw n;let p=await e.scanImage(a,
{qrEngine:c,canvas:d,disallowCanvasResizing:f});return l?p:p.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._updateScanRegionHighlight();this.$scanRegionHighlight&&(this.$scanRegionHighlight.style.display="");this._scanFrame()}_onLoadedMetaData(){this._scanRegion=this._calculateScanRegion(this.$video);this._updateScanRegionHighlight()}_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}}_updateScanRegionHighlight(){if(this.$scanRegionHighlight){var a=this.$video,b=a.videoWidth,c=a.videoHeight;this.$scanRegionHighlight.style.width=`${(this._scanRegion.width||b)/b*100}%`;this.$scanRegionHighlight.style.height=`${(this._scanRegion.height||c)/c*100}%`;this.$scanRegionHighlight.style.top=`${(this._scanRegion.y||
0)/c*100}%`;a=/scaleX\(-1\)/.test(a.style.transform);this.$scanRegionHighlight.style[a?"right":"left"]=`${(this._scanRegion.x||0)/b*100}%`;this.$scanRegionHighlight.style[a?"left":"right"]="unset"}}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,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,g,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
//# sourceMappingURL=qr-scanner.min.js.map
File diff suppressed because one or more lines are too long
+24 -20
View File
@@ -1,23 +1,27 @@
'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})
typeof d?d:this._calculateScanRegion);this._preferredCamera=b.preferredCamera||f||this._preferredCamera;this._legacyCanvasSize="number"===typeof c?c:"number"===typeof d?d:this._legacyCanvasSize;b.highlightScanRegion&&(this.$scanRegionHighlight=document.createElement("div"),this.$scanRegionHighlight.classList.add("scan-region-highlight"),this.$scanRegionHighlight.style.position="absolute",this.$scanRegionHighlight.style.display="none",this.$scanRegionHighlight.style.outline="rgba(255, 255, 255, .3) solid 7px");
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);let k=a.parentElement;requestAnimationFrame(()=>{let l=window.getComputedStyle(a),n=window.getComputedStyle(k);"none"===l.display&&(a.style.setProperty("display",
"block","important"),g=!0);"visible"!==l.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",delete this.$scanRegionHighlight);if(this.$scanRegionHighlight){["relative","absolute","fixed","sticky"].includes(n.position)||(console.warn("QrScanner has overwritten the video parent's position for correct display of the scan region highlight. To avoid this, set the position to relative, absolute, fixed or sticky."),
k.style.setProperty("position","absolute","important"));if(n.width!==l.width||n.height!==l.height)console.warn("QrScanner has overwritten the video parent's width and height for correct display of the scan region highlight. To avoid this, make the parent as big as the video or the video as big as the parent."),k.style.setProperty("width","max-content","important"),k.style.setProperty("height","max-content","important");this._updateScanRegionHighlight();k.appendChild(this.$scanRegionHighlight)}});
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();this.$scanRegionHighlight&&(this.$scanRegionHighlight.style.display="none");let b=()=>{this.$video.srcObject instanceof MediaStream&&(e._stopVideoStream(this.$video.srcObject),this.$video.srcObject=null)};if(a)return b(),!0;await new Promise(c=>setTimeout(c,300));if(!this._paused)return!1;
b();return!0}async setCamera(a){a!==this._preferredCamera&&(this._preferredCamera=a,await this._restartVideoStream())}static async scanImage(a,b,c,d,f=!1,g=!1){let k,l=!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,l=!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 n,p;[c,n]=await Promise.all([c||e.createQrEngine(),e._loadImage(a)]);[d,p]=e._drawToCanvas(n,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,q;r=m=>{"qrResult"===m.data.type&&(h.removeEventListener("message",r),h.removeEventListener("error",
q),clearTimeout(u),null!==m.data.data?t(l?{data:m.data.data,cornerPoints:e._convertPoints(m.data.cornerPoints,k)}:m.data.data):v(e.NO_QR_CODE_FOUND))};q=m=>{h.removeEventListener("message",r);h.removeEventListener("error",q);clearTimeout(u);v("Scanner error: "+(m?m.message||m:"Unknown Error"))};h.addEventListener("message",r);h.addEventListener("error",q);u=setTimeout(()=>q("timeout"),1E4);let w=p.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 l?{data:h.rawValue,cornerPoints:e._convertPoints(h.cornerPoints,k)}:h.rawValue}catch(h){throw`Scanner error: ${h.message||h}`;}})()])}catch(n){if(!k||!g)throw n;let p=await e.scanImage(a,{qrEngine:c,canvas:d,disallowCanvasResizing:f});return l?p:p.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._updateScanRegionHighlight();this.$scanRegionHighlight&&
(this.$scanRegionHighlight.style.display="");this._scanFrame()}_onLoadedMetaData(){this._scanRegion=this._calculateScanRegion(this.$video);this._updateScanRegionHighlight()}_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}}_updateScanRegionHighlight(){if(this.$scanRegionHighlight){var a=
this.$video,b=a.videoWidth,c=a.videoHeight;this.$scanRegionHighlight.style.width=`${(this._scanRegion.width||b)/b*100}%`;this.$scanRegionHighlight.style.height=`${(this._scanRegion.height||c)/c*100}%`;this.$scanRegionHighlight.style.top=`${(this._scanRegion.y||0)/c*100}%`;a=/scaleX\(-1\)/.test(a.style.transform);this.$scanRegionHighlight.style[a?"right":"left"]=`${(this._scanRegion.x||0)/b*100}%`;this.$scanRegionHighlight.style[a?"left":"right"]="unset"}}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,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,g,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})
//# sourceMappingURL=qr-scanner.umd.min.js.map
File diff suppressed because one or more lines are too long
+63 -5
View File
@@ -46,8 +46,9 @@ export default class QrScanner {
}
}
$video: HTMLVideoElement;
$canvas: HTMLCanvasElement;
readonly $video: HTMLVideoElement;
readonly $canvas: HTMLCanvasElement;
readonly $scanRegionHighlight?: HTMLDivElement;
private readonly _onDecode?: (result: QrScanner.ScanResult) => void;
private readonly _legacyOnDecode?: (result: string) => void;
private readonly _legacyCanvasSize: number = QrScanner.DEFAULT_CANVAS_SIZE;
@@ -66,6 +67,7 @@ export default class QrScanner {
onDecodeError?: (error: Error | string) => void,
calculateScanRegion?: (video: HTMLVideoElement) => QrScanner.ScanRegion,
preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,
highlightScanRegion?: boolean,
/** just a temporary flag until we switch entirely to the new api */
returnDetailedScanResult?: true,
},
@@ -95,6 +97,7 @@ export default class QrScanner {
onDecodeError?: (error: Error | string) => void,
calculateScanRegion?: (video: HTMLVideoElement) => QrScanner.ScanRegion,
preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,
highlightScanRegion?: boolean,
/** just a temporary flag until we switch entirely to the new api */
returnDetailedScanResult?: true,
},
@@ -139,6 +142,14 @@ export default class QrScanner {
? canvasSizeOrCalculateScanRegion
: this._legacyCanvasSize;
if (options.highlightScanRegion) {
this.$scanRegionHighlight = document.createElement('div');
this.$scanRegionHighlight.classList.add('scan-region-highlight');
this.$scanRegionHighlight.style.position = 'absolute';
this.$scanRegionHighlight.style.display = 'none';
// default style; can be overwritten via css
this.$scanRegionHighlight.style.outline = 'rgba(255, 255, 255, .3) solid 7px';
}
this._scanRegion = this._calculateScanRegion(video);
this._onPlay = this._onPlay.bind(this);
@@ -166,14 +177,16 @@ export default class QrScanner {
document.body.appendChild(video);
shouldHideVideo = true;
}
const videoContainer = video.parentElement!;
requestAnimationFrame(() => {
// Checking in requestAnimationFrame which should avoid a potential additional re-flow for getComputedStyle.
const computedStyle = window.getComputedStyle(video);
if (computedStyle.display === 'none') {
const videoStyle = window.getComputedStyle(video);
const containerStyle = window.getComputedStyle(videoContainer);
if (videoStyle.display === 'none') {
video.style.setProperty('display', 'block', 'important');
shouldHideVideo = true;
}
if (computedStyle.visibility !== 'visible') {
if (videoStyle.visibility !== 'visible') {
video.style.setProperty('visibility', 'visible', 'important');
shouldHideVideo = true;
}
@@ -183,6 +196,27 @@ export default class QrScanner {
video.style.opacity = '0';
video.style.width = '0';
video.style.height = '0';
// @ts-ignore
delete this.$scanRegionHighlight!;
}
if (this.$scanRegionHighlight) {
if (!['relative', 'absolute', 'fixed', 'sticky'].includes(containerStyle.position!)) {
console.warn('QrScanner has overwritten the video parent\'s position for correct display of the '
+ 'scan region highlight. To avoid this, set the position to relative, absolute, fixed or '
+ 'sticky.');
videoContainer.style.setProperty('position', 'absolute', 'important');
}
if (containerStyle.width !== videoStyle.width
|| containerStyle.height !== videoStyle.height) {
console.warn('QrScanner has overwritten the video parent\'s width and height for correct display '
+ 'of the scan region highlight. To avoid this, make the parent as big as the video or the '
+ 'video as big as the parent.');
videoContainer.style.setProperty('width', 'max-content', 'important');
videoContainer.style.setProperty('height', 'max-content', 'important');
}
this._updateScanRegionHighlight();
videoContainer.appendChild(this.$scanRegionHighlight!);
}
});
@@ -314,6 +348,10 @@ export default class QrScanner {
if (!this._active) return true;
this.$video.pause();
if (this.$scanRegionHighlight) {
this.$scanRegionHighlight.style.display = 'none';
}
const stopStream = () => {
if (this.$video.srcObject instanceof MediaStream) {
// revoke srcObject only if it's a stream which was likely set by us
@@ -522,11 +560,16 @@ export default class QrScanner {
private _onPlay(): void {
this._scanRegion = this._calculateScanRegion(this.$video);
this._updateScanRegionHighlight();
if (this.$scanRegionHighlight) {
this.$scanRegionHighlight.style.display = '';
}
this._scanFrame();
}
private _onLoadedMetaData(): void {
this._scanRegion = this._calculateScanRegion(this.$video);
this._updateScanRegionHighlight();
}
private _onVisibilityChange(): void {
@@ -551,6 +594,21 @@ export default class QrScanner {
};
}
private _updateScanRegionHighlight(): void {
if (!this.$scanRegionHighlight) return;
const video = this.$video;
const videoWidth = video.videoWidth;
const videoHeight = video.videoHeight;
this.$scanRegionHighlight.style.width = `${(this._scanRegion.width || videoWidth) / videoWidth * 100}%`;
this.$scanRegionHighlight.style.height = `${(this._scanRegion.height || videoHeight) / videoHeight * 100}%`;
this.$scanRegionHighlight.style.top = `${(this._scanRegion.y || 0) / videoHeight * 100}%`;
const isVideoMirrored = /scaleX\(-1\)/.test(video.style.transform!);
this.$scanRegionHighlight.style[isVideoMirrored ? 'right' : 'left'] =
`${(this._scanRegion.x || 0) / videoWidth * 100}%`;
this.$scanRegionHighlight.style[isVideoMirrored ? 'left' : 'right'] = 'unset';
}
private static _convertPoints(
points: QrScanner.Point[],
scanRegion?: QrScanner.ScanRegion | null,
+5 -2
View File
@@ -5,8 +5,9 @@ export default class QrScanner {
static WORKER_PATH: string;
static hasCamera(): Promise<boolean>;
static listCameras(requestLabels?: boolean): Promise<Array<QrScanner.Camera>>;
$video: HTMLVideoElement;
$canvas: HTMLCanvasElement;
readonly $video: HTMLVideoElement;
readonly $canvas: HTMLCanvasElement;
readonly $scanRegionHighlight?: HTMLDivElement;
private readonly _onDecode?;
private readonly _legacyOnDecode?;
private readonly _legacyCanvasSize;
@@ -21,6 +22,7 @@ export default class QrScanner {
onDecodeError?: (error: Error | string) => void;
calculateScanRegion?: (video: HTMLVideoElement) => QrScanner.ScanRegion;
preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId;
highlightScanRegion?: boolean;
/** just a temporary flag until we switch entirely to the new api */
returnDetailedScanResult?: true;
});
@@ -58,6 +60,7 @@ export default class QrScanner {
private _onLoadedMetaData;
private _onVisibilityChange;
private _calculateScanRegion;
private _updateScanRegionHighlight;
private static _convertPoints;
private _scanFrame;
private _onDecodeError;