mirror of
https://github.com/nimiq/qr-scanner.git
synced 2026-07-28 04:06:51 +00:00
Convert to ES2017 with async/await
Also saves 4.3% in size for qr-scanner.min.js
This commit is contained in:
@@ -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: ~50.5 kB (~13.0 kB gzipped) minified with Google's closure compiler. If the native `BarcodeDetector` is available, only ~6.8 kB (~2.7 kB gzipped) are loaded.
|
||||
- Lightweight: ~53.3 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.
|
||||
- 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.
|
||||
|
||||
Vendored
+19
-19
@@ -1,20 +1,20 @@
|
||||
class f{static hasCamera(){return f.listCameras(!1).then(a=>!!a.length).catch(()=>!1)}static listCameras(a=!1){if(!navigator.mediaDevices)return Promise.resolve([]);let b=null;return(a?navigator.mediaDevices.getUserMedia({audio:!1,video:!0}).then(a=>b=a).catch(()=>{}):Promise.resolve()).then(()=>navigator.mediaDevices.enumerateDevices()).then(a=>a.filter(a=>"videoinput"===a.kind).map((a,b)=>({id:a.deviceId,label:a.label||(0===b?"Default Camera":`Camera ${b+1}`)}))).finally(()=>{if(b)for(let a of b.getTracks())a.stop(),
|
||||
b.removeTrack(a)})}constructor(a,b,c=this._onDecodeError,d=this._calculateScanRegion,g="environment"){this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;this._legacyCanvasSize=f.DEFAULT_CANVAS_SIZE;this._preferredCamera=g;this._flashOn=this._paused=this._active=!1;"number"===typeof c?(this._legacyCanvasSize=c,console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future")):this._onDecodeError=c;"number"===typeof d?(this._legacyCanvasSize=
|
||||
d,console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future")):this._calculateScanRegion=d;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 b=window.getComputedStyle(a);"none"===b.display&&(a.style.setProperty("display","block","important"),h=!0);"visible"!==b.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=f.createQrEngine()}hasFlash(){let a=null;return(this.$video.srcObject?Promise.resolve(this.$video.srcObject.getVideoTracks()[0]):this._getCameraStream().then(({stream:b})=>{console.warn("Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream");a=b;return b.getVideoTracks()[0]})).then(a=>"torch"in a.getSettings()).catch(()=>!1).finally(()=>{if(a)for(let b of a.getTracks())b.stop(),a.removeTrack(b)})}isFlashOn(){return this._flashOn}toggleFlash(){return this._flashOn?
|
||||
this.turnFlashOff():this.turnFlashOn()}turnFlashOn(){if(this._flashOn)return Promise.resolve();this._flashOn=!0;return!this._active||this._paused?Promise.resolve():this.hasFlash().then(a=>a?this.$video.srcObject.getVideoTracks()[0].applyConstraints({advanced:[{torch:!0}]}):Promise.reject("No flash available")).catch(()=>{this._flashOn=!1;throw e;})}turnFlashOff(){if(this._flashOn)return this._flashOn=!1,this._restartVideoStream()}destroy(){this.$video.removeEventListener("loadedmetadata",this._onLoadedMetaData);
|
||||
this.$video.removeEventListener("play",this._onPlay);document.removeEventListener("visibilitychange",this._onVisibilityChange);this.stop();f._postWorkerMessage(this._qrEnginePromise,"close")}start(){if(this._active&&!this._paused)return Promise.resolve();"https:"!==window.location.protocol&&console.warn("The camera stream is only accessible if the page is transferred via https.");this._active=!0;if(document.hidden)return Promise.resolve();this._paused=!1;return this.$video.srcObject?(this.$video.play(),
|
||||
Promise.resolve()):this._getCameraStream().then(({stream:a,facingMode:b})=>{this.$video.srcObject=a;this.$video.play();this._setVideoMirror(b);this._flashOn&&(this._flashOn=!1,this.turnFlashOn().catch(()=>{}))}).catch(a=>{this._active=!1;throw a;})}stop(){this.pause();this._active=!1}pause(a=!1){this._paused=!0;if(!this._active)return Promise.resolve(!0);this.$video.pause();let b=()=>{const a=this.$video.srcObject?this.$video.srcObject.getTracks():[];for(const b of a)b.stop(),this.$video.srcObject.removeTrack(b);
|
||||
this.$video.srcObject=null};return a?(b(),Promise.resolve(!0)):(new Promise(a=>setTimeout(a,300))).then(()=>{if(!this._paused)return!1;b();return!0})}setCamera(a){if(a===this._preferredCamera)return Promise.resolve();this._preferredCamera=a;return this._restartVideoStream()}static scanImage(a,b=null,c=null,d=null,g=!1,h=!1){let k=c instanceof Worker,l=Promise.all([c||f.createQrEngine(),f._loadImage(a)]).then(([a,h])=>{c=a;let l;[d,l]=this._drawToCanvas(h,b,d,g);return c instanceof Worker?(k||c.postMessage({type:"inversionMode",
|
||||
data:"both"}),new Promise((a,b)=>{let g,h,k;h=d=>{"qrResult"===d.data.type&&(c.removeEventListener("message",h),c.removeEventListener("error",k),clearTimeout(g),null!==d.data.data?a(d.data.data):b(f.NO_QR_CODE_FOUND))};k=a=>{c.removeEventListener("message",h);c.removeEventListener("error",k);clearTimeout(g);b("Scanner error: "+(a?a.message||a:"Unknown Error"))};c.addEventListener("message",h);c.addEventListener("error",k);g=setTimeout(()=>k("timeout"),1E4);let m=l.getImageData(0,0,d.width,d.height);
|
||||
c.postMessage({type:"decode",data:m},[m.data.buffer])})):new Promise((a,b)=>{let g=setTimeout(()=>b("Scanner error: timeout"),1E4);c.detect(d).then(c=>{c.length?a(c[0].rawValue):b(f.NO_QR_CODE_FOUND)}).catch(a=>b("Scanner error: "+(a.message||a))).finally(()=>clearTimeout(g))})});b&&h&&(l=l.catch(()=>f.scanImage(a,null,c,d,g)));return l=l.finally(()=>{k||f._postWorkerMessage(c,"close")})}setGrayscaleWeights(a,b,c,d=!0){f._postWorkerMessage(this._qrEnginePromise,"grayscaleWeights",{red:a,green:b,blue:c,
|
||||
useIntegerApproximation:d})}setInversionMode(a){f._postWorkerMessage(this._qrEnginePromise,"inversionMode",a)}static createQrEngine(a=f.WORKER_PATH){return("BarcodeDetector"in window&&BarcodeDetector.getSupportedFormats?BarcodeDetector.getSupportedFormats():Promise.resolve([])).then(b=>-1!==b.indexOf("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(){if(!this._active||this.$video.paused||this.$video.ended)return!1;requestAnimationFrame(()=>{1>=this.$video.readyState?this._scanFrame():this._qrEnginePromise.then(a=>f.scanImage(this.$video,this._scanRegion,
|
||||
a,this.$canvas)).then(this._onDecode,a=>{this._active&&(-1!==(a.message||a).indexOf("service unavailable")&&(this._qrEnginePromise=f.createQrEngine()),this._onDecodeError(a))}).then(()=>this._scanFrame())})}_onDecodeError(a){a!==f.NO_QR_CODE_FOUND&&console.log(a)}_getCameraStream(){if(!navigator.mediaDevices)return Promise.reject("Camera not found.");let a="environment"===this._preferredCamera||"user"===this._preferredCamera?"facingMode":"deviceId",b=[{width:{min:1024}},{width:{min:768}},{}];return[...b.map(b=>
|
||||
Object.assign({},b,{[a]:{exact:this._preferredCamera}})),...b].reduceRight((a,b)=>()=>navigator.mediaDevices.getUserMedia({video:b,audio:!1}).then(a=>({stream:a,facingMode:this._getFacingMode(a)||(b.facingMode?this._preferredCamera:"environment"===this._preferredCamera?"user":"environment")})).catch(a),()=>Promise.reject("Camera not found."))()}_restartVideoStream(){let a=this._paused;return this.pause(!0).then(b=>{if(b&&!a&&this._active)return this.start()})}_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=null,c=null,d=!1){c=c||document.createElement("canvas");let g=b&&b.x?b.x:0,h=b&&b.y?b.y:0,k=b&&b.width?b.width:a.width||a.videoWidth,l=b&&b.height?b.height:a.height||a.videoHeight;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,g,h,k,l,0,0,c.width,c.height);return[c,b]}static _loadImage(a){if(a instanceof HTMLCanvasElement||a instanceof HTMLVideoElement||window.ImageBitmap&&a instanceof window.ImageBitmap||window.OffscreenCanvas&&a instanceof window.OffscreenCanvas)return Promise.resolve(a);if(a instanceof Image)return f._awaitImageLoad(a).then(()=>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;return f._awaitImageLoad(b).then(()=>{(a instanceof File||a instanceof Blob)&&URL.revokeObjectURL(b.src);return b})}return Promise.reject("Unsupported image type.")}static _awaitImageLoad(a){return new Promise((b,c)=>{if(a.complete&&0!==a.naturalWidth)b();else{let d,g;d=()=>{a.removeEventListener("load",d);a.removeEventListener("error",g);b()};g=()=>{a.removeEventListener("load",d);a.removeEventListener("error",
|
||||
g);c("Image load error")};a.addEventListener("load",d);a.addEventListener("error",g)}})}static _postWorkerMessage(a,b,c){return Promise.resolve(a).then(a=>{a instanceof Worker&&a.postMessage({type:b,data:c})})}}f.DEFAULT_CANVAS_SIZE=400;f.NO_QR_CODE_FOUND="No QR code found";f.WORKER_PATH="qr-scanner-worker.min.js";export default f
|
||||
class e{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=null;if(a)try{b=await navigator.mediaDevices.getUserMedia({audio:!1,video:!0})}catch(c){}try{return(await navigator.mediaDevices.enumerateDevices()).filter(a=>"videoinput"===a.kind).map((a,b)=>({id:a.deviceId,label:a.label||(0===b?"Default Camera":`Camera ${b+1}`)}))}finally{if(b)for(let a of b.getTracks())a.stop(),b.removeTrack(a)}}constructor(a,
|
||||
b,c=this._onDecodeError,d=this._calculateScanRegion,g="environment"){this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;this._legacyCanvasSize=e.DEFAULT_CANVAS_SIZE;this._preferredCamera=g;this._flashOn=this._paused=this._active=!1;"number"===typeof c?(this._legacyCanvasSize=c,console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future")):this._onDecodeError=c;"number"===typeof d?(this._legacyCanvasSize=d,console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future")):
|
||||
this._calculateScanRegion=d;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 f=!1;a.hidden&&(a.hidden=!1,f=!0);document.body.contains(a)||(document.body.appendChild(a),f=!0);requestAnimationFrame(()=>{let b=window.getComputedStyle(a);"none"===b.display&&(a.style.setProperty("display","block",
|
||||
"important"),f=!0);"visible"!==b.visibility&&(a.style.setProperty("visibility","visible","important"),f=!0);f&&(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()}async hasFlash(){let a=
|
||||
null;try{return a=this.$video.srcObject||(await this._getCameraStream()).stream,"torch"in a.getVideoTracks()[0].getSettings()}catch(b){return!1}finally{if(a&&a!==this.$video.srcObject){console.warn("Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream");for(let b of a.getTracks())b.stop(),a.removeTrack(b)}}}isFlashOn(){return this._flashOn}async toggleFlash(){this._flashOn?await this.turnFlashOff():await this.turnFlashOn()}async turnFlashOn(){if(!this._flashOn&&
|
||||
(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.stop();e._postWorkerMessage(this._qrEnginePromise,"close")}async start(){if(!this._active||this._paused)if("https:"!==window.location.protocol&&console.warn("The camera stream is only accessible if the page is transferred via https."),this._active=!0,!document.hidden)if(this._paused=!1,this.$video.srcObject)this.$video.play();else try{let {stream:a,facingMode:b}=await this._getCameraStream();this.$video.srcObject=a;this.$video.play();this._setVideoMirror(b);this._flashOn&&
|
||||
(this._flashOn=!1,this.turnFlashOn().catch(()=>{}))}catch(a){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=()=>{const a=this.$video.srcObject?this.$video.srcObject.getTracks():[];for(const b of a)b.stop(),this.$video.srcObject.removeTrack(b);this.$video.srcObject=null};if(a)return b(),!0;await new Promise(a=>setTimeout(a,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=null,c=null,d=null,g=!1,f=!1){let k=!!c;try{let f,m;[c,f]=await Promise.all([c||e.createQrEngine(),e._loadImage(a)]);[d,m]=this._drawToCanvas(f,b,d,g);return c instanceof Worker?(k||c.postMessage({type:"inversionMode",data:"both"}),await new Promise((a,b)=>{let g,f,h;f=d=>{"qrResult"===d.data.type&&(c.removeEventListener("message",f),c.removeEventListener("error",h),clearTimeout(g),null!==d.data.data?a(d.data.data):
|
||||
b(e.NO_QR_CODE_FOUND))};h=a=>{c.removeEventListener("message",f);c.removeEventListener("error",h);clearTimeout(g);b("Scanner error: "+(a?a.message||a:"Unknown Error"))};c.addEventListener("message",f);c.addEventListener("error",h);g=setTimeout(()=>h("timeout"),1E4);let k=m.getImageData(0,0,d.width,d.height);c.postMessage({type:"decode",data:k},[k.data.buffer])})):await Promise.race([new Promise((a,b)=>window.setTimeout(()=>b("Scanner error: timeout"),1E4)),(async()=>{try{let [a]=await c.detect(d);
|
||||
if(!a)throw e.NO_QR_CODE_FOUND;return a.rawValue}catch(h){throw`Scanner error: ${h instanceof Error?h.message:h}`;}})()])}catch(l){if(!b||!f)throw l;return await e.scanImage(a,null,c,d,g)}finally{k||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,this._scanRegion,this._qrEnginePromise,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"===this._preferredCamera||"user"===this._preferredCamera?"facingMode":"deviceId",b=[{width:{min:1024}},{width:{min:768}},{}],c=b.map(b=>Object.assign({},b,{[a]:{exact:this._preferredCamera}}));for(let a of[...c,...b])try{let b=await navigator.mediaDevices.getUserMedia({video:a,audio:!1}),c=this._getFacingMode(b)||
|
||||
(a.facingMode?this._preferredCamera:"environment"===this._preferredCamera?"user":"environment");return{stream:b,facingMode:c}}catch(g){}throw"Camera not found.";}async _restartVideoStream(){let a=this._paused;await this.pause(!0)&&!a&&this._active&&await this.start()}_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=null,c=null,d=!1){c=c||document.createElement("canvas");let g=b&&b.x?b.x:0,f=b&&b.y?b.y:0,k=b&&b.width?b.width:a.width||a.videoWidth,l=b&&b.height?b.height:a.height||a.videoHeight;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,g,f,k,l,0,0,c.width,c.height);return[c,b]}static async _loadImage(a){if(a instanceof HTMLCanvasElement||
|
||||
a instanceof HTMLVideoElement||window.ImageBitmap&&a instanceof window.ImageBitmap||window.OffscreenCanvas&&a instanceof window.OffscreenCanvas)return a;if(a instanceof Image)return await e._awaitImageLoad(a),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;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,g;d=()=>{a.removeEventListener("load",d);a.removeEventListener("error",g);b()};g=()=>{a.removeEventListener("load",d);a.removeEventListener("error",g);c("Image load error")};a.addEventListener("load",d);a.addEventListener("error",g)})}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
Vendored
+19
-19
@@ -1,20 +1,20 @@
|
||||
'use strict';(function(d,a){"object"===typeof exports&&"undefined"!==typeof module?module.exports=a():"function"===typeof define&&define.amd?define(a):(d=d||self,d.QrScanner=a())})(this,function(){class d{static hasCamera(){return d.listCameras(!1).then(a=>!!a.length).catch(()=>!1)}static listCameras(a=!1){if(!navigator.mediaDevices)return Promise.resolve([]);let b=null;return(a?navigator.mediaDevices.getUserMedia({audio:!1,video:!0}).then(a=>b=a).catch(()=>{}):Promise.resolve()).then(()=>navigator.mediaDevices.enumerateDevices()).then(a=>
|
||||
a.filter(a=>"videoinput"===a.kind).map((a,b)=>({id:a.deviceId,label:a.label||(0===b?"Default Camera":`Camera ${b+1}`)}))).finally(()=>{if(b)for(let a of b.getTracks())a.stop(),b.removeTrack(a)})}constructor(a,b,c=this._onDecodeError,f=this._calculateScanRegion,k="environment"){this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;this._legacyCanvasSize=d.DEFAULT_CANVAS_SIZE;this._preferredCamera=k;this._flashOn=this._paused=this._active=!1;"number"===typeof c?(this._legacyCanvasSize=
|
||||
c,console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future")):this._onDecodeError=c;"number"===typeof f?(this._legacyCanvasSize=f,console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future")):this._calculateScanRegion=f;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 b=window.getComputedStyle(a);"none"===b.display&&(a.style.setProperty("display","block","important"),g=!0);"visible"!==b.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=d.createQrEngine()}hasFlash(){let a=null;return(this.$video.srcObject?Promise.resolve(this.$video.srcObject.getVideoTracks()[0]):this._getCameraStream().then(({stream:b})=>{console.warn("Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream");
|
||||
a=b;return b.getVideoTracks()[0]})).then(a=>"torch"in a.getSettings()).catch(()=>!1).finally(()=>{if(a)for(let b of a.getTracks())b.stop(),a.removeTrack(b)})}isFlashOn(){return this._flashOn}toggleFlash(){return this._flashOn?this.turnFlashOff():this.turnFlashOn()}turnFlashOn(){if(this._flashOn)return Promise.resolve();this._flashOn=!0;return!this._active||this._paused?Promise.resolve():this.hasFlash().then(a=>a?this.$video.srcObject.getVideoTracks()[0].applyConstraints({advanced:[{torch:!0}]}):Promise.reject("No flash available")).catch(()=>
|
||||
{this._flashOn=!1;throw e;})}turnFlashOff(){if(this._flashOn)return this._flashOn=!1,this._restartVideoStream()}destroy(){this.$video.removeEventListener("loadedmetadata",this._onLoadedMetaData);this.$video.removeEventListener("play",this._onPlay);document.removeEventListener("visibilitychange",this._onVisibilityChange);this.stop();d._postWorkerMessage(this._qrEnginePromise,"close")}start(){if(this._active&&!this._paused)return Promise.resolve();"https:"!==window.location.protocol&&console.warn("The camera stream is only accessible if the page is transferred via https.");
|
||||
this._active=!0;if(document.hidden)return Promise.resolve();this._paused=!1;return this.$video.srcObject?(this.$video.play(),Promise.resolve()):this._getCameraStream().then(({stream:a,facingMode:b})=>{this.$video.srcObject=a;this.$video.play();this._setVideoMirror(b);this._flashOn&&(this._flashOn=!1,this.turnFlashOn().catch(()=>{}))}).catch(a=>{this._active=!1;throw a;})}stop(){this.pause();this._active=!1}pause(a=!1){this._paused=!0;if(!this._active)return Promise.resolve(!0);this.$video.pause();
|
||||
let b=()=>{const a=this.$video.srcObject?this.$video.srcObject.getTracks():[];for(const b of a)b.stop(),this.$video.srcObject.removeTrack(b);this.$video.srcObject=null};return a?(b(),Promise.resolve(!0)):(new Promise(a=>setTimeout(a,300))).then(()=>{if(!this._paused)return!1;b();return!0})}setCamera(a){if(a===this._preferredCamera)return Promise.resolve();this._preferredCamera=a;return this._restartVideoStream()}static scanImage(a,b=null,c=null,f=null,k=!1,g=!1){let h=c instanceof Worker,l=Promise.all([c||
|
||||
d.createQrEngine(),d._loadImage(a)]).then(([a,g])=>{c=a;let l;[f,l]=this._drawToCanvas(g,b,f,k);return c instanceof Worker?(h||c.postMessage({type:"inversionMode",data:"both"}),new Promise((a,b)=>{let k,g,h;g=f=>{"qrResult"===f.data.type&&(c.removeEventListener("message",g),c.removeEventListener("error",h),clearTimeout(k),null!==f.data.data?a(f.data.data):b(d.NO_QR_CODE_FOUND))};h=a=>{c.removeEventListener("message",g);c.removeEventListener("error",h);clearTimeout(k);b("Scanner error: "+(a?a.message||
|
||||
a:"Unknown Error"))};c.addEventListener("message",g);c.addEventListener("error",h);k=setTimeout(()=>h("timeout"),1E4);let m=l.getImageData(0,0,f.width,f.height);c.postMessage({type:"decode",data:m},[m.data.buffer])})):new Promise((a,b)=>{let k=setTimeout(()=>b("Scanner error: timeout"),1E4);c.detect(f).then(c=>{c.length?a(c[0].rawValue):b(d.NO_QR_CODE_FOUND)}).catch(a=>b("Scanner error: "+(a.message||a))).finally(()=>clearTimeout(k))})});b&&g&&(l=l.catch(()=>d.scanImage(a,null,c,f,k)));return l=l.finally(()=>
|
||||
{h||d._postWorkerMessage(c,"close")})}setGrayscaleWeights(a,b,c,f=!0){d._postWorkerMessage(this._qrEnginePromise,"grayscaleWeights",{red:a,green:b,blue:c,useIntegerApproximation:f})}setInversionMode(a){d._postWorkerMessage(this._qrEnginePromise,"inversionMode",a)}static createQrEngine(a=d.WORKER_PATH){return("BarcodeDetector"in window&&BarcodeDetector.getSupportedFormats?BarcodeDetector.getSupportedFormats():Promise.resolve([])).then(b=>-1!==b.indexOf("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(){if(!this._active||
|
||||
this.$video.paused||this.$video.ended)return!1;requestAnimationFrame(()=>{1>=this.$video.readyState?this._scanFrame():this._qrEnginePromise.then(a=>d.scanImage(this.$video,this._scanRegion,a,this.$canvas)).then(this._onDecode,a=>{this._active&&(-1!==(a.message||a).indexOf("service unavailable")&&(this._qrEnginePromise=d.createQrEngine()),this._onDecodeError(a))}).then(()=>this._scanFrame())})}_onDecodeError(a){a!==d.NO_QR_CODE_FOUND&&console.log(a)}_getCameraStream(){if(!navigator.mediaDevices)return Promise.reject("Camera not found.");
|
||||
let a="environment"===this._preferredCamera||"user"===this._preferredCamera?"facingMode":"deviceId",b=[{width:{min:1024}},{width:{min:768}},{}];return[...b.map(b=>Object.assign({},b,{[a]:{exact:this._preferredCamera}})),...b].reduceRight((a,b)=>()=>navigator.mediaDevices.getUserMedia({video:b,audio:!1}).then(a=>({stream:a,facingMode:this._getFacingMode(a)||(b.facingMode?this._preferredCamera:"environment"===this._preferredCamera?"user":"environment")})).catch(a),()=>Promise.reject("Camera not found."))()}_restartVideoStream(){let a=
|
||||
this._paused;return this.pause(!0).then(b=>{if(b&&!a&&this._active)return this.start()})}_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=null,c=null,f=!1){c=c||document.createElement("canvas");let d=b&&b.x?b.x:0,g=b&&b.y?b.y:0,h=b&&b.width?b.width:a.width||a.videoWidth,l=b&&b.height?b.height:
|
||||
a.height||a.videoHeight;f||(f=b&&b.downScaledWidth?b.downScaledWidth:h,b=b&&b.downScaledHeight?b.downScaledHeight:l,c.width!==f&&(c.width=f),c.height!==b&&(c.height=b));b=c.getContext("2d",{alpha:!1});b.imageSmoothingEnabled=!1;b.drawImage(a,d,g,h,l,0,0,c.width,c.height);return[c,b]}static _loadImage(a){if(a instanceof HTMLCanvasElement||a instanceof HTMLVideoElement||window.ImageBitmap&&a instanceof window.ImageBitmap||window.OffscreenCanvas&&a instanceof window.OffscreenCanvas)return Promise.resolve(a);
|
||||
if(a instanceof Image)return d._awaitImageLoad(a).then(()=>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;return d._awaitImageLoad(b).then(()=>{(a instanceof File||a instanceof Blob)&&URL.revokeObjectURL(b.src);return b})}return Promise.reject("Unsupported image type.")}static _awaitImageLoad(a){return new Promise((b,c)=>{if(a.complete&&0!==a.naturalWidth)b();else{let f,d;f=()=>
|
||||
{a.removeEventListener("load",f);a.removeEventListener("error",d);b()};d=()=>{a.removeEventListener("load",f);a.removeEventListener("error",d);c("Image load error")};a.addEventListener("load",f);a.addEventListener("error",d)}})}static _postWorkerMessage(a,b,c){return Promise.resolve(a).then(a=>{a instanceof Worker&&a.postMessage({type:b,data:c})})}}d.DEFAULT_CANVAS_SIZE=400;d.NO_QR_CODE_FOUND="No QR code found";d.WORKER_PATH="qr-scanner-worker.min.js";return d})
|
||||
'use strict';(function(d,a){"object"===typeof exports&&"undefined"!==typeof module?module.exports=a():"function"===typeof define&&define.amd?define(a):(d="undefined"!==typeof globalThis?globalThis:d||self,d.QrScanner=a())})(this,function(){class d{static async hasCamera(){try{return!!(await d.listCameras(!1)).length}catch(a){return!1}}static async listCameras(a=!1){if(!navigator.mediaDevices)return[];let b=null;if(a)try{b=await navigator.mediaDevices.getUserMedia({audio:!1,video:!0})}catch(c){}try{return(await navigator.mediaDevices.enumerateDevices()).filter(a=>
|
||||
"videoinput"===a.kind).map((a,b)=>({id:a.deviceId,label:a.label||(0===b?"Default Camera":`Camera ${b+1}`)}))}finally{if(b)for(let a of b.getTracks())a.stop(),b.removeTrack(a)}}constructor(a,b,c=this._onDecodeError,e=this._calculateScanRegion,g="environment"){this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;this._legacyCanvasSize=d.DEFAULT_CANVAS_SIZE;this._preferredCamera=g;this._flashOn=this._paused=this._active=!1;"number"===typeof c?(this._legacyCanvasSize=c,console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future")):
|
||||
this._onDecodeError=c;"number"===typeof e?(this._legacyCanvasSize=e,console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future")):this._calculateScanRegion=e;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 f=!1;a.hidden&&(a.hidden=!1,
|
||||
f=!0);document.body.contains(a)||(document.body.appendChild(a),f=!0);requestAnimationFrame(()=>{let b=window.getComputedStyle(a);"none"===b.display&&(a.style.setProperty("display","block","important"),f=!0);"visible"!==b.visibility&&(a.style.setProperty("visibility","visible","important"),f=!0);f&&(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=d.createQrEngine()}async hasFlash(){let a=null;try{return a=this.$video.srcObject||(await this._getCameraStream()).stream,"torch"in a.getVideoTracks()[0].getSettings()}catch(b){return!1}finally{if(a&&a!==this.$video.srcObject){console.warn("Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream");for(let b of a.getTracks())b.stop(),a.removeTrack(b)}}}isFlashOn(){return this._flashOn}async toggleFlash(){this._flashOn?
|
||||
await this.turnFlashOff():await this.turnFlashOn()}async turnFlashOn(){if(!this._flashOn&&(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.stop();d._postWorkerMessage(this._qrEnginePromise,"close")}async start(){if(!this._active||this._paused)if("https:"!==window.location.protocol&&console.warn("The camera stream is only accessible if the page is transferred via https."),this._active=!0,!document.hidden)if(this._paused=!1,this.$video.srcObject)this.$video.play();else try{let {stream:a,facingMode:b}=await this._getCameraStream();this.$video.srcObject=
|
||||
a;this.$video.play();this._setVideoMirror(b);this._flashOn&&(this._flashOn=!1,this.turnFlashOn().catch(()=>{}))}catch(a){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=()=>{const a=this.$video.srcObject?this.$video.srcObject.getTracks():[];for(const b of a)b.stop(),this.$video.srcObject.removeTrack(b);this.$video.srcObject=null};if(a)return b(),!0;await new Promise(a=>setTimeout(a,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=null,c=null,e=null,g=!1,f=!1){let k=!!c;try{let f,m;[c,f]=await Promise.all([c||d.createQrEngine(),d._loadImage(a)]);[e,m]=this._drawToCanvas(f,b,e,g);return c instanceof Worker?(k||c.postMessage({type:"inversionMode",data:"both"}),await new Promise((a,b)=>{let g,f,h;f=e=>{"qrResult"===e.data.type&&(c.removeEventListener("message",f),c.removeEventListener("error",
|
||||
h),clearTimeout(g),null!==e.data.data?a(e.data.data):b(d.NO_QR_CODE_FOUND))};h=a=>{c.removeEventListener("message",f);c.removeEventListener("error",h);clearTimeout(g);b("Scanner error: "+(a?a.message||a:"Unknown Error"))};c.addEventListener("message",f);c.addEventListener("error",h);g=setTimeout(()=>h("timeout"),1E4);let k=m.getImageData(0,0,e.width,e.height);c.postMessage({type:"decode",data:k},[k.data.buffer])})):await Promise.race([new Promise((a,b)=>window.setTimeout(()=>b("Scanner error: timeout"),
|
||||
1E4)),(async()=>{try{let [a]=await c.detect(e);if(!a)throw d.NO_QR_CODE_FOUND;return a.rawValue}catch(h){throw`Scanner error: ${h instanceof Error?h.message:h}`;}})()])}catch(l){if(!b||!f)throw l;return await d.scanImage(a,null,c,e,g)}finally{k||d._postWorkerMessage(c,"close")}}setGrayscaleWeights(a,b,c,e=!0){d._postWorkerMessage(this._qrEnginePromise,"grayscaleWeights",{red:a,green:b,blue:c,useIntegerApproximation:e})}setInversionMode(a){d._postWorkerMessage(this._qrEnginePromise,"inversionMode",
|
||||
a)}static async createQrEngine(a=d.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 d.scanImage(this.$video,this._scanRegion,this._qrEnginePromise,this.$canvas)}catch(b){if(!this._active)return;(b.message||b).includes("service unavailable")&&
|
||||
(this._qrEnginePromise=d.createQrEngine());this._onDecodeError(b)}a&&this._onDecode(a)}this._scanFrame()})}_onDecodeError(a){a!==d.NO_QR_CODE_FOUND&&console.log(a)}async _getCameraStream(){if(!navigator.mediaDevices)throw"Camera not found.";let a="environment"===this._preferredCamera||"user"===this._preferredCamera?"facingMode":"deviceId",b=[{width:{min:1024}},{width:{min:768}},{}],c=b.map(b=>Object.assign({},b,{[a]:{exact:this._preferredCamera}}));for(let a of[...c,...b])try{let b=await navigator.mediaDevices.getUserMedia({video:a,
|
||||
audio:!1}),c=this._getFacingMode(b)||(a.facingMode?this._preferredCamera:"environment"===this._preferredCamera?"user":"environment");return{stream:b,facingMode:c}}catch(g){}throw"Camera not found.";}async _restartVideoStream(){let a=this._paused;await this.pause(!0)&&!a&&this._active&&await this.start()}_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=null,c=null,d=!1){c=c||document.createElement("canvas");let e=b&&b.x?b.x:0,f=b&&b.y?b.y:0,k=b&&b.width?b.width:a.width||a.videoWidth,l=b&&b.height?b.height:a.height||a.videoHeight;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,e,f,k,l,0,0,c.width,c.height);return[c,b]}static async _loadImage(a){if(a instanceof
|
||||
HTMLCanvasElement||a instanceof HTMLVideoElement||window.ImageBitmap&&a instanceof window.ImageBitmap||window.OffscreenCanvas&&a instanceof window.OffscreenCanvas)return a;if(a instanceof Image)return await d._awaitImageLoad(a),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;try{return await d._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,g;d=()=>{a.removeEventListener("load",d);a.removeEventListener("error",g);b()};g=()=>{a.removeEventListener("load",d);a.removeEventListener("error",g);c("Image load error")};a.addEventListener("load",d);a.addEventListener("error",g)})}static async _postWorkerMessage(a,b,c){a=await a;a instanceof Worker&&a.postMessage({type:b,data:c})}}d.DEFAULT_CANVAS_SIZE=400;d.NO_QR_CODE_FOUND="No QR code found";d.WORKER_PATH=
|
||||
"qr-scanner-worker.min.js";return d})
|
||||
//# sourceMappingURL=qr-scanner.umd.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
+2
-2
@@ -18,8 +18,8 @@ export default [{
|
||||
}],
|
||||
plugins: [
|
||||
closureCompiler({
|
||||
language_in: 'ECMASCRIPT6',
|
||||
language_out: 'ECMASCRIPT6',
|
||||
language_in: 'ECMASCRIPT_2017',
|
||||
language_out: 'ECMASCRIPT_2017',
|
||||
rewrite_polyfills: false,
|
||||
})
|
||||
]
|
||||
|
||||
+214
-232
@@ -1,41 +1,43 @@
|
||||
export default class QrScanner {
|
||||
/* async */
|
||||
static hasCamera() {
|
||||
return QrScanner.listCameras(false)
|
||||
.then(cameras => !!cameras.length)
|
||||
.catch(() => false);
|
||||
static async hasCamera() {
|
||||
try {
|
||||
return !!(await QrScanner.listCameras(false)).length;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* async */
|
||||
static listCameras(requestLabels = false) {
|
||||
if (!navigator.mediaDevices) return Promise.resolve([]);
|
||||
static async listCameras(requestLabels = false) {
|
||||
if (!navigator.mediaDevices) return [];
|
||||
|
||||
// Note that enumerateDevices can always be called and does not prompt the user for permission.
|
||||
// However, enumerateDevices only includes device labels if served via https and an active media stream exists
|
||||
// or permission to access the camera was given. Therefore, ask for camera permission by opening a stream, if
|
||||
// labels were requested.
|
||||
let openedStream = null;
|
||||
return (requestLabels
|
||||
? navigator.mediaDevices.getUserMedia({ audio: false, video: true })
|
||||
.then(stream => openedStream = stream)
|
||||
if (requestLabels) {
|
||||
try {
|
||||
openedStream = await navigator.mediaDevices.getUserMedia({ audio: false, video: true });
|
||||
} catch (e) {
|
||||
// Fail gracefully, especially if the device has no camera or on mobile when the camera is already in
|
||||
// use and some browsers disallow a second stream.
|
||||
.catch(() => {})
|
||||
: Promise.resolve()
|
||||
)
|
||||
.then(() => navigator.mediaDevices.enumerateDevices())
|
||||
.then(devices => devices.filter(device => device.kind === 'videoinput').map((device, i) => ({
|
||||
}
|
||||
}
|
||||
try {
|
||||
const devices = await navigator.mediaDevices.enumerateDevices();
|
||||
return devices.filter(device => device.kind === 'videoinput').map((device, i) => ({
|
||||
id: device.deviceId,
|
||||
label: device.label || (i === 0 ? 'Default Camera' : `Camera ${i + 1}`),
|
||||
})))
|
||||
.finally(() => {
|
||||
// close the stream we just opened for getting camera access for listing the device labels
|
||||
if (!openedStream) return;
|
||||
}));
|
||||
} finally {
|
||||
// close the stream we just opened for getting camera access for listing the device labels
|
||||
if (openedStream) {
|
||||
for (const track of openedStream.getTracks()) {
|
||||
track.stop();
|
||||
openedStream.removeTrack(track);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
constructor(
|
||||
@@ -124,68 +126,61 @@ export default class QrScanner {
|
||||
this._qrEnginePromise = QrScanner.createQrEngine();
|
||||
}
|
||||
|
||||
/* async */
|
||||
hasFlash() {
|
||||
let openedStream = null;
|
||||
return (this.$video.srcObject
|
||||
? Promise.resolve(this.$video.srcObject.getVideoTracks()[0])
|
||||
: this._getCameraStream().then(({ stream }) => {
|
||||
async hasFlash() {
|
||||
let stream = null;
|
||||
try {
|
||||
stream = this.$video.srcObject || (await this._getCameraStream()).stream;
|
||||
return 'torch' in stream.getVideoTracks()[0].getSettings();
|
||||
} catch (e) {
|
||||
return false;
|
||||
} finally {
|
||||
// close the stream we just opened for detecting whether it supports flash
|
||||
if (stream && stream !== this.$video.srcObject) {
|
||||
console.warn('Call hasFlash after successfully starting the scanner to avoid creating '
|
||||
+ 'a temporary video stream');
|
||||
openedStream = stream;
|
||||
return stream.getVideoTracks()[0];
|
||||
})
|
||||
)
|
||||
.then((track) => 'torch' in track.getSettings())
|
||||
.catch(() => false)
|
||||
.finally(() => {
|
||||
// close the stream we just opened for detecting whether it supports flash
|
||||
if (!openedStream) return;
|
||||
for (const track of openedStream.getTracks()) {
|
||||
for (const track of stream.getTracks()) {
|
||||
track.stop();
|
||||
openedStream.removeTrack(track);
|
||||
stream.removeTrack(track);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
isFlashOn() {
|
||||
return this._flashOn;
|
||||
}
|
||||
|
||||
/* async */
|
||||
toggleFlash() {
|
||||
if (this._flashOn) {
|
||||
return this.turnFlashOff();
|
||||
} else {
|
||||
return this.turnFlashOn();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* async */
|
||||
turnFlashOn() {
|
||||
if (this._flashOn) return Promise.resolve();
|
||||
this._flashOn = true;
|
||||
if (!this._active || this._paused) return Promise.resolve(); // flash will be turned on later on .start()
|
||||
return this.hasFlash().then((hasFlash) => {
|
||||
if (!hasFlash) return Promise.reject('No flash available');
|
||||
// Note that the video track is guaranteed to exist at this point
|
||||
return this.$video.srcObject.getVideoTracks()[0].applyConstraints({
|
||||
advanced: [{ torch: true }],
|
||||
});
|
||||
}).catch(() => {
|
||||
this._flashOn = false;
|
||||
throw e;
|
||||
});
|
||||
isFlashOn() {
|
||||
return this._flashOn;
|
||||
}
|
||||
|
||||
/* async */
|
||||
turnFlashOff() {
|
||||
async toggleFlash() {
|
||||
if (this._flashOn) {
|
||||
await this.turnFlashOff();
|
||||
} else {
|
||||
await this.turnFlashOn();
|
||||
}
|
||||
}
|
||||
|
||||
async turnFlashOn() {
|
||||
if (this._flashOn) return;
|
||||
this._flashOn = true;
|
||||
if (!this._active || this._paused) return; // flash will be turned on later on .start()
|
||||
try {
|
||||
if (!await this.hasFlash()) throw 'No flash available';
|
||||
// Note that the video track is guaranteed to exist at this point
|
||||
await this.$video.srcObject.getVideoTracks()[0].applyConstraints({
|
||||
advanced: [{ torch: true }],
|
||||
});
|
||||
} catch (e) {
|
||||
this._flashOn = false;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
async turnFlashOff() {
|
||||
if (!this._flashOn) return;
|
||||
// applyConstraints with torch: false does not work to turn the flashlight off, as a stream's torch stays
|
||||
// continuously on, see https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#torch. Therefore,
|
||||
// we have to stop the stream to turn the flashlight off.
|
||||
this._flashOn = false;
|
||||
return this._restartVideoStream();
|
||||
await this._restartVideoStream();
|
||||
}
|
||||
|
||||
destroy() {
|
||||
@@ -197,43 +192,38 @@ export default class QrScanner {
|
||||
QrScanner._postWorkerMessage(this._qrEnginePromise, 'close');
|
||||
}
|
||||
|
||||
/* async */
|
||||
start() {
|
||||
if (this._active && !this._paused) {
|
||||
return Promise.resolve();
|
||||
}
|
||||
async start() {
|
||||
if (this._active && !this._paused) return;
|
||||
|
||||
if (window.location.protocol !== 'https:') {
|
||||
// warn but try starting the camera anyways
|
||||
console.warn('The camera stream is only accessible if the page is transferred via https.');
|
||||
}
|
||||
|
||||
this._active = true;
|
||||
if (document.hidden) {
|
||||
// camera will be started as soon as tab is in foreground
|
||||
return Promise.resolve();
|
||||
}
|
||||
if (document.hidden) return; // camera will be started as soon as tab is in foreground
|
||||
this._paused = false;
|
||||
if (this.$video.srcObject) {
|
||||
// camera stream already/still set
|
||||
this.$video.play();
|
||||
return Promise.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
return this._getCameraStream()
|
||||
.then(({ stream, facingMode }) => {
|
||||
this.$video.srcObject = stream;
|
||||
this.$video.play();
|
||||
this._setVideoMirror(facingMode);
|
||||
try {
|
||||
const { stream, facingMode } = await this._getCameraStream();
|
||||
this.$video.srcObject = stream;
|
||||
this.$video.play();
|
||||
this._setVideoMirror(facingMode);
|
||||
|
||||
// Restart the flash if it was previously on
|
||||
if (this._flashOn) {
|
||||
this._flashOn = false; // force turnFlashOn to restart the flash
|
||||
this.turnFlashOn().catch(() => {});
|
||||
}
|
||||
})
|
||||
.catch(e => {
|
||||
this._active = false;
|
||||
throw e;
|
||||
});
|
||||
// Restart the flash if it was previously on
|
||||
if (this._flashOn) {
|
||||
this._flashOn = false; // force turnFlashOn to restart the flash
|
||||
this.turnFlashOn().catch(() => {});
|
||||
}
|
||||
} catch (e) {
|
||||
this._active = false;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
@@ -241,12 +231,9 @@ export default class QrScanner {
|
||||
this._active = false;
|
||||
}
|
||||
|
||||
/* async */
|
||||
pause(stopStreamImmediately = false) {
|
||||
async pause(stopStreamImmediately = false) {
|
||||
this._paused = true;
|
||||
if (!this._active) {
|
||||
return Promise.resolve(true);
|
||||
}
|
||||
if (!this._active) return true;
|
||||
this.$video.pause();
|
||||
|
||||
const stopStream = () => {
|
||||
@@ -260,44 +247,47 @@ export default class QrScanner {
|
||||
|
||||
if (stopStreamImmediately) {
|
||||
stopStream();
|
||||
return Promise.resolve(true);
|
||||
return true;
|
||||
}
|
||||
|
||||
return new Promise((resolve) => setTimeout(resolve, 300))
|
||||
.then(() => {
|
||||
if (!this._paused) return false;
|
||||
stopStream();
|
||||
return true;
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 300));
|
||||
if (!this._paused) return false;
|
||||
stopStream();
|
||||
return true;
|
||||
}
|
||||
|
||||
/* async */
|
||||
setCamera(facingModeOrDeviceId) {
|
||||
if (facingModeOrDeviceId === this._preferredCamera) return Promise.resolve();
|
||||
async setCamera(facingModeOrDeviceId) {
|
||||
if (facingModeOrDeviceId === this._preferredCamera) return;
|
||||
this._preferredCamera = facingModeOrDeviceId;
|
||||
// Restart the scanner with the new camera which will also update the video mirror and the scan region.
|
||||
return this._restartVideoStream();
|
||||
await this._restartVideoStream();
|
||||
}
|
||||
|
||||
/* async */
|
||||
static scanImage(imageOrFileOrUrl, scanRegion=null, qrEngine=null, canvas=null, disallowCanvasResizing=false,
|
||||
alsoTryWithoutScanRegion=false) {
|
||||
const gotExternalWorker = qrEngine instanceof Worker;
|
||||
static async scanImage(
|
||||
imageOrFileOrUrl,
|
||||
scanRegion = null,
|
||||
qrEngine = null,
|
||||
canvas = null,
|
||||
disallowCanvasResizing = false,
|
||||
alsoTryWithoutScanRegion = false
|
||||
) {
|
||||
const gotExternalEngine = !!qrEngine;
|
||||
|
||||
let promise = Promise.all([
|
||||
qrEngine || QrScanner.createQrEngine(),
|
||||
QrScanner._loadImage(imageOrFileOrUrl),
|
||||
]).then(([engine, image]) => {
|
||||
qrEngine = engine;
|
||||
try {
|
||||
let image;
|
||||
let canvasContext;
|
||||
[qrEngine, image] = await Promise.all([
|
||||
qrEngine || QrScanner.createQrEngine(),
|
||||
QrScanner._loadImage(imageOrFileOrUrl),
|
||||
]);
|
||||
[canvas, canvasContext] = this._drawToCanvas(image, scanRegion, canvas, disallowCanvasResizing);
|
||||
|
||||
if (qrEngine instanceof Worker) {
|
||||
if (!gotExternalWorker) {
|
||||
if (!gotExternalEngine) {
|
||||
// Enable scanning of inverted color qr codes. Not using _postWorkerMessage as it's async
|
||||
qrEngine.postMessage({ type: 'inversionMode', data: 'both' });
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
return await new Promise((resolve, reject) => {
|
||||
let timeout, onMessage, onError;
|
||||
onMessage = event => {
|
||||
if (event.data.type !== 'qrResult') {
|
||||
@@ -329,30 +319,27 @@ export default class QrScanner {
|
||||
}, [imageData.data.buffer]);
|
||||
});
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
const timeout = setTimeout(() => reject('Scanner error: timeout'), 10000);
|
||||
qrEngine.detect(canvas).then(scanResults => {
|
||||
if (!scanResults.length) {
|
||||
reject(QrScanner.NO_QR_CODE_FOUND);
|
||||
} else {
|
||||
resolve(scanResults[0].rawValue);
|
||||
return await Promise.race([
|
||||
new Promise((resolve, reject) => window.setTimeout(() => reject('Scanner error: timeout'), 10000)),
|
||||
(async () => {
|
||||
try {
|
||||
const [scanResult] = await qrEngine.detect(canvas);
|
||||
if (!scanResult) throw QrScanner.NO_QR_CODE_FOUND;
|
||||
return scanResult.rawValue;
|
||||
} catch (e) {
|
||||
throw `Scanner error: ${e instanceof Error ? e.message : e}`;
|
||||
}
|
||||
}).catch((e) => reject('Scanner error: ' + (e.message || e))).finally(() => clearTimeout(timeout));
|
||||
});
|
||||
})(),
|
||||
]);
|
||||
}
|
||||
} catch (e) {
|
||||
if (!scanRegion || !alsoTryWithoutScanRegion) throw e;
|
||||
return await QrScanner.scanImage(imageOrFileOrUrl, null, qrEngine, canvas, disallowCanvasResizing);
|
||||
} finally {
|
||||
if (!gotExternalEngine) {
|
||||
QrScanner._postWorkerMessage(qrEngine, 'close');
|
||||
}
|
||||
});
|
||||
|
||||
if (scanRegion && alsoTryWithoutScanRegion) {
|
||||
promise = promise.catch(() =>
|
||||
QrScanner.scanImage(imageOrFileOrUrl, null, qrEngine, canvas, disallowCanvasResizing));
|
||||
}
|
||||
|
||||
promise = promise.finally(() => {
|
||||
if (gotExternalWorker) return;
|
||||
QrScanner._postWorkerMessage(qrEngine, 'close');
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
setGrayscaleWeights(red, green, blue, useIntegerApproximation = true) {
|
||||
@@ -371,16 +358,11 @@ export default class QrScanner {
|
||||
QrScanner._postWorkerMessage(this._qrEnginePromise, 'inversionMode', inversionMode);
|
||||
}
|
||||
|
||||
/* async */
|
||||
static createQrEngine(workerPath = QrScanner.WORKER_PATH) {
|
||||
return ('BarcodeDetector' in window && BarcodeDetector.getSupportedFormats
|
||||
? BarcodeDetector.getSupportedFormats()
|
||||
: Promise.resolve([])
|
||||
)
|
||||
.then((supportedFormats) => supportedFormats.indexOf('qr_code') !== -1
|
||||
? new BarcodeDetector({ formats: ['qr_code'] })
|
||||
: new Worker(workerPath)
|
||||
);
|
||||
static async createQrEngine(workerPath = QrScanner.WORKER_PATH) {
|
||||
const hasNativeBarcodeDetector = 'BarcodeDetector' in window && BarcodeDetector.getSupportedFormats
|
||||
? (await BarcodeDetector.getSupportedFormats()).includes('qr_code')
|
||||
: false;
|
||||
return hasNativeBarcodeDetector ? new BarcodeDetector({ formats: ['qr_code'] }) : new Worker(workerPath);
|
||||
}
|
||||
|
||||
_onPlay() {
|
||||
@@ -415,9 +397,9 @@ export default class QrScanner {
|
||||
}
|
||||
|
||||
_scanFrame() {
|
||||
if (!this._active || this.$video.paused || this.$video.ended) return false;
|
||||
if (!this._active || this.$video.paused || this.$video.ended) return;
|
||||
// using requestAnimationFrame to avoid scanning if tab is in background
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(async () => {
|
||||
if (this.$video.readyState <= 1) {
|
||||
// Skip scans until the video is ready as drawImage() only works correctly on a video with readyState
|
||||
// > 1, see https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/drawImage#Notes.
|
||||
@@ -426,18 +408,30 @@ export default class QrScanner {
|
||||
this._scanFrame();
|
||||
return;
|
||||
}
|
||||
this._qrEnginePromise
|
||||
.then((qrEngine) => QrScanner.scanImage(this.$video, this._scanRegion, qrEngine, this.$canvas))
|
||||
.then(this._onDecode, (error) => {
|
||||
if (!this._active) return;
|
||||
const errorMessage = error.message || error;
|
||||
if (errorMessage.indexOf('service unavailable') !== -1) {
|
||||
// When the native BarcodeDetector crashed, create a new one
|
||||
this._qrEnginePromise = QrScanner.createQrEngine();
|
||||
}
|
||||
this._onDecodeError(error);
|
||||
})
|
||||
.then(() => this._scanFrame());
|
||||
|
||||
let result;
|
||||
try {
|
||||
result = await QrScanner.scanImage(
|
||||
this.$video,
|
||||
this._scanRegion,
|
||||
this._qrEnginePromise,
|
||||
this.$canvas
|
||||
);
|
||||
} catch (error) {
|
||||
if (!this._active) return;
|
||||
const errorMessage = error.message || error;
|
||||
if (errorMessage.includes('service unavailable')) {
|
||||
// When the native BarcodeDetector crashed, create a new one
|
||||
this._qrEnginePromise = QrScanner.createQrEngine();
|
||||
}
|
||||
this._onDecodeError(error);
|
||||
}
|
||||
|
||||
if (result) {
|
||||
this._onDecode(result);
|
||||
}
|
||||
|
||||
this._scanFrame();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -447,11 +441,8 @@ export default class QrScanner {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
/* async */
|
||||
_getCameraStream() {
|
||||
if (!navigator.mediaDevices) {
|
||||
return Promise.reject('Camera not found.');
|
||||
}
|
||||
async _getCameraStream() {
|
||||
if (!navigator.mediaDevices) throw 'Camera not found.';
|
||||
|
||||
const preferenceType = this._preferredCamera === 'environment' || this._preferredCamera === 'user'
|
||||
? 'facingMode'
|
||||
@@ -465,38 +456,34 @@ export default class QrScanner {
|
||||
[preferenceType]: { exact: this._preferredCamera },
|
||||
}));
|
||||
|
||||
// First try constraints with camera, then without camera. Using reduceRight as the Promise is build in a
|
||||
// bottom up fashion.
|
||||
return [...constraintsWithCamera, ...constraintsWithoutCamera].reduceRight((fallback, constraint) =>
|
||||
() => navigator.mediaDevices.getUserMedia({ video: constraint, audio: false })
|
||||
.then((stream) => ({
|
||||
stream,
|
||||
// Try to determine the facing mode from the stream, otherwise use a guess or 'environment' as
|
||||
// default. Note that the guess is not always accurate as Safari returns cameras of different facing
|
||||
// mode, even for exact facingMode constraints.
|
||||
facingMode: this._getFacingMode(stream)
|
||||
|| (constraint.facingMode
|
||||
? this._preferredCamera // _preferredCamera is a facing mode and we are able to fulfill it
|
||||
: (this._preferredCamera === 'environment'
|
||||
? 'user' // switch as _preferredCamera was environment but we are not able to fulfill it
|
||||
: 'environment' // switch from unfulfilled user facingMode or default to environment
|
||||
)
|
||||
),
|
||||
}))
|
||||
.catch(fallback),
|
||||
() => Promise.reject('Camera not found.')
|
||||
)();
|
||||
for (const constraints of [...constraintsWithCamera, ...constraintsWithoutCamera]) {
|
||||
try {
|
||||
const stream = await navigator.mediaDevices.getUserMedia({ video: constraints, audio: false });
|
||||
// Try to determine the facing mode from the stream, otherwise use a guess or 'environment' as
|
||||
// default. Note that the guess is not always accurate as Safari returns cameras of different facing
|
||||
// mode, even for exact facingMode constraints.
|
||||
const facingMode = this._getFacingMode(stream)
|
||||
|| (constraints.facingMode
|
||||
? this._preferredCamera // _preferredCamera is a facing mode and we are able to fulfill it
|
||||
: (this._preferredCamera === 'environment'
|
||||
? 'user' // switch as _preferredCamera was environment but we are not able to fulfill it
|
||||
: 'environment' // switch from unfulfilled user facingMode or default to environment
|
||||
)
|
||||
);
|
||||
return { stream, facingMode };
|
||||
} catch (e) {}
|
||||
}
|
||||
|
||||
throw 'Camera not found.';
|
||||
}
|
||||
|
||||
/* async */
|
||||
_restartVideoStream() {
|
||||
async _restartVideoStream() {
|
||||
// Note that we always pause the stream and not only if !this._paused as even if this._paused === true, the
|
||||
// stream might still be running, as it's by default only stopped after a delay of 300ms.
|
||||
const wasPaused = this._paused;
|
||||
return this.pause(true).then((paused) => {
|
||||
if (!paused || wasPaused || !this._active) return;
|
||||
return this.start();
|
||||
});
|
||||
const paused = await this.pause(true);
|
||||
if (!paused || wasPaused || !this._active) return;
|
||||
await this.start();
|
||||
}
|
||||
|
||||
_setVideoMirror(facingMode) {
|
||||
@@ -550,14 +537,14 @@ export default class QrScanner {
|
||||
return [canvas, context];
|
||||
}
|
||||
|
||||
/* async */
|
||||
static _loadImage(imageOrFileOrBlobOrUrl) {
|
||||
static async _loadImage(imageOrFileOrBlobOrUrl) {
|
||||
if (imageOrFileOrBlobOrUrl instanceof HTMLCanvasElement || imageOrFileOrBlobOrUrl instanceof HTMLVideoElement
|
||||
|| window.ImageBitmap && imageOrFileOrBlobOrUrl instanceof window.ImageBitmap
|
||||
|| window.OffscreenCanvas && imageOrFileOrBlobOrUrl instanceof window.OffscreenCanvas) {
|
||||
return Promise.resolve(imageOrFileOrBlobOrUrl);
|
||||
return imageOrFileOrBlobOrUrl;
|
||||
} else if (imageOrFileOrBlobOrUrl instanceof Image) {
|
||||
return QrScanner._awaitImageLoad(imageOrFileOrBlobOrUrl).then(() => imageOrFileOrBlobOrUrl);
|
||||
await QrScanner._awaitImageLoad(imageOrFileOrBlobOrUrl);
|
||||
return imageOrFileOrBlobOrUrl;
|
||||
} else if (imageOrFileOrBlobOrUrl instanceof File || imageOrFileOrBlobOrUrl instanceof Blob
|
||||
|| imageOrFileOrBlobOrUrl instanceof URL || typeof(imageOrFileOrBlobOrUrl)==='string') {
|
||||
const image = new Image();
|
||||
@@ -566,47 +553,42 @@ export default class QrScanner {
|
||||
} else {
|
||||
image.src = imageOrFileOrBlobOrUrl;
|
||||
}
|
||||
return QrScanner._awaitImageLoad(image).then(() => {
|
||||
try {
|
||||
await QrScanner._awaitImageLoad(image);
|
||||
return image;
|
||||
} finally {
|
||||
if (imageOrFileOrBlobOrUrl instanceof File || imageOrFileOrBlobOrUrl instanceof Blob) {
|
||||
URL.revokeObjectURL(image.src);
|
||||
}
|
||||
return image;
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return Promise.reject('Unsupported image type.');
|
||||
throw 'Unsupported image type.';
|
||||
}
|
||||
}
|
||||
|
||||
/* async */
|
||||
static _awaitImageLoad(image) {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (image.complete && image.naturalWidth!==0) {
|
||||
// already loaded
|
||||
static async _awaitImageLoad(image) {
|
||||
if (image.complete && image.naturalWidth !== 0) return; // already loaded
|
||||
await new Promise((resolve, reject) => {
|
||||
let onLoad, onError;
|
||||
onLoad = () => {
|
||||
image.removeEventListener('load', onLoad);
|
||||
image.removeEventListener('error', onError);
|
||||
resolve();
|
||||
} else {
|
||||
let onLoad, onError;
|
||||
onLoad = () => {
|
||||
image.removeEventListener('load', onLoad);
|
||||
image.removeEventListener('error', onError);
|
||||
resolve();
|
||||
};
|
||||
onError = () => {
|
||||
image.removeEventListener('load', onLoad);
|
||||
image.removeEventListener('error', onError);
|
||||
reject('Image load error');
|
||||
};
|
||||
image.addEventListener('load', onLoad);
|
||||
image.addEventListener('error', onError);
|
||||
}
|
||||
};
|
||||
onError = () => {
|
||||
image.removeEventListener('load', onLoad);
|
||||
image.removeEventListener('error', onError);
|
||||
reject('Image load error');
|
||||
};
|
||||
image.addEventListener('load', onLoad);
|
||||
image.addEventListener('error', onError);
|
||||
});
|
||||
}
|
||||
|
||||
/* async */
|
||||
static _postWorkerMessage(qrEngineOrQrEnginePromise, type, data) {
|
||||
return Promise.resolve(qrEngineOrQrEnginePromise).then((qrEngine) => {
|
||||
if (!(qrEngine instanceof Worker)) return;
|
||||
qrEngine.postMessage({ type, data });
|
||||
});
|
||||
static async _postWorkerMessage(qrEngineOrQrEnginePromise, type, data) {
|
||||
const qrEngine = await qrEngineOrQrEnginePromise;
|
||||
if (!(qrEngine instanceof Worker)) return;
|
||||
qrEngine.postMessage({ type, data });
|
||||
}
|
||||
}
|
||||
QrScanner.DEFAULT_CANVAS_SIZE = 400;
|
||||
|
||||
Reference in New Issue
Block a user