mirror of
https://github.com/nimiq/qr-scanner.git
synced 2026-07-28 04:06:51 +00:00
21 lines
9.2 KiB
JavaScript
21 lines
9.2 KiB
JavaScript
class e{constructor(a,b,c,d,g="environment"){this._legacyCanvasSize=e.DEFAULT_CANVAS_SIZE;this._flashOn=this._paused=this._active=!1;c=c||this._onDecodeError;d=d||this._calculateScanRegion;if(a.srcObject&&!(a.srcObject instanceof MediaStream))throw"Only MediaStreams are supported as video source.";this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;this._preferredCamera=g;"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 k=window.getComputedStyle(a);"none"===k.display&&(a.style.setProperty("display","block","important"),h=!0);"visible"!==k.visibility&&(a.style.setProperty("visibility","visible","important"),h=!0);h&&(console.warn("QrScanner has overwritten the video hiding style to avoid Safari stopping the playback."),a.style.opacity="0",a.style.width="0",a.style.height="0")});a.addEventListener("play",this._onPlay);
|
|
a.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrEnginePromise=e.createQrEngine()}static async hasCamera(){try{return!!(await e.listCameras(!1)).length}catch(a){return!1}}static async listCameras(a=!1){if(!navigator.mediaDevices)return[];let b;if(a)try{b=await navigator.mediaDevices.getUserMedia({audio:!1,video:!0})}catch(c){}try{return(await navigator.mediaDevices.enumerateDevices()).filter(c=>"videoinput"===
|
|
c.kind).map((c,d)=>({id:c.deviceId,label:c.label||(0===d?"Default Camera":`Camera ${d+1}`)}))}finally{if(b)for(let c of b.getTracks())c.stop(),b.removeTrack(c)}}async hasFlash(){let a;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 c=this.$video.srcObject?this.$video.srcObject.getTracks():[];for(const d of c)d.stop(),this.$video.srcObject.removeTrack(d);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,g=!1,h=!1){let k=!!c;try{let l,t;[c,l]=await Promise.all([c||e.createQrEngine(),e._loadImage(a)]);[d,t]=e._drawToCanvas(l,b,d,g);if(c instanceof Worker){let f=c;k||f.postMessage({type:"inversionMode",data:"both"});return await new Promise((q,u)=>{let r,p,n;p=m=>{"qrResult"===m.data.type&&
|
|
(f.removeEventListener("message",p),f.removeEventListener("error",n),clearTimeout(r),null!==m.data.data?q(m.data.data):u(e.NO_QR_CODE_FOUND))};n=m=>{f.removeEventListener("message",p);f.removeEventListener("error",n);clearTimeout(r);u("Scanner error: "+(m?m.message||m:"Unknown Error"))};f.addEventListener("message",p);f.addEventListener("error",n);r=setTimeout(()=>n("timeout"),1E4);let v=t.getImageData(0,0,d.width,d.height);f.postMessage({type:"decode",data:v},[v.data.buffer])})}return await Promise.race([new Promise((f,
|
|
q)=>window.setTimeout(()=>q("Scanner error: timeout"),1E4)),(async()=>{try{let [f]=await c.detect(d);if(!f)throw e.NO_QR_CODE_FOUND;return f.rawValue}catch(f){throw`Scanner error: ${f.message||f}`;}})()])}catch(l){if(!b||!h)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|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 g=await navigator.mediaDevices.getUserMedia({video:d,
|
|
audio:!1}),h=this._getFacingMode(g)||(d.facingMode?this._preferredCamera:"environment"===this._preferredCamera?"user":"environment");return{stream:g,facingMode:h}}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,c,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.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,g,h,k,l,0,0,c.width,c.height);return[c,b]}static async _loadImage(a){if(a instanceof
|
|
Image)return await e._awaitImageLoad(a),a;if(a instanceof HTMLVideoElement||a instanceof HTMLCanvasElement||a instanceof SVGImageElement||"OffscreenCanvas"in window&&a instanceof OffscreenCanvas||"ImageBitmap"in window&&a instanceof ImageBitmap)return a;if(a instanceof File||a instanceof Blob||a instanceof URL||"string"===typeof a){let b=new Image;b.src=a instanceof File||a instanceof Blob?URL.createObjectURL(a):a.toString();try{return await e._awaitImageLoad(b),b}finally{(a instanceof File||a instanceof
|
|
Blob)&&URL.revokeObjectURL(b.src)}}else throw"Unsupported image type.";}static async _awaitImageLoad(a){a.complete&&0!==a.naturalWidth||await new Promise((b,c)=>{let d=g=>{a.removeEventListener("load",d);a.removeEventListener("error",d);g 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
|