Don't open a second stream if we already have permission for device listing

This is an optimization but also a fix for iOS Safari which stops an already ongoing
stream when a second stream is opened, see https://bugs.webkit.org/show_bug.cgi?id=179363.

Fixes #125
This commit is contained in:
Daniel committed 2022-01-25 10:21:26 +01:00
1 parent a3e87bcf1e
commit d704d3ab37
8 files changed
+76 -70

No files matched your search

+1 -1
View File
@@ -205,7 +205,7 @@ QrScanner.hasCamera(); // async
This library provides a utility method for getting a list of the device's cameras, defined via their `id` and `label`. This can be useful for letting a user choose a specific camera to use.
You can optionally request the camera's labels. Note that this however requires the user's permission to acess the cameras, which he will be asked for if not granted already. If not specifically requested, device labels are determined on a best effort basis, i.e. actual labels are returned if permissions were already granted and fallback labels otherwise.
You can optionally request the camera's labels. Note that this however requires the user's permission to access the cameras, which he will be asked for if not granted already. If not specifically requested, device labels are determined on a best effort basis, i.e. actual labels are returned if permissions were already granted and fallback labels otherwise. If you want to request camera labels, it's recommendable to call `listCameras` after a QrScanner instance was successfully started, as by then the user will already have given his permission.
```js
QrScanner.listCameras(); // async; without requesting camera labels
QrScanner.listCameras(true); // async; requesting camera labels, potentially asking the user for permission
+18 -17
View File
@@ -20,21 +20,22 @@ c.asyncExecutePromiseGenerator=function(a){function b(e){return a.next(e)}functi
class g{constructor(a,b,d,e,f="environment"){this._legacyCanvasSize=g.DEFAULT_CANVAS_SIZE;this._flashOn=this._paused=this._active=!1;d=d||this._onDecodeError;e=e||this._calculateScanRegion;this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;this._preferredCamera=f;"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._onDecodeError=d;"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 h=!1;a.hidden&&(a.hidden=!1,h=!0);document.body.contains(a)||(document.body.appendChild(a),h=
!0);requestAnimationFrame(()=>{let k=window.getComputedStyle(a);"none"===k.display&&(a.style.setProperty("display","block","important"),h=!0);"visible"!==k.visibility&&(a.style.setProperty("visibility","visible","important"),h=!0);h&&(console.warn("QrScanner has overwritten the video hiding style to avoid Safari stopping the playback."),a.style.opacity="0",a.style.width="0",a.style.height="0")});a.addEventListener("play",this._onPlay);a.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",
this._onVisibilityChange);this._qrEnginePromise=g.createQrEngine()}static hasCamera(){return c.asyncExecutePromiseGeneratorFunction(function*(){try{return!!(yield g.listCameras(!1)).length}catch(a){return!1}})}static listCameras(a=!1){return c.asyncExecutePromiseGeneratorFunction(function*(){if(!navigator.mediaDevices)return[];let b;if(a)try{b=yield navigator.mediaDevices.getUserMedia({audio:!1,video:!0})}catch(d){}try{return(yield navigator.mediaDevices.enumerateDevices()).filter(d=>"videoinput"===
d.kind).map((d,e)=>({id:d.deviceId,label:d.label||(0===e?"Default Camera":`Camera ${e+1}`)}))}finally{if(b)for(let d of b.getTracks())d.stop(),b.removeTrack(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{if(b&&b!==a.$video.srcObject){console.warn("Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream");
for(let d of b.getTracks())d.stop(),b.removeTrack(d)}}})}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._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.stop();g._postWorkerMessage(this._qrEnginePromise,"close")}start(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){if(!a._active||
a._paused)if("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)a.$video.play();else try{let {stream:b,facingMode:d}=yield a._getCameraStream();a.$video.srcObject=b;a.$video.play();a._setVideoMirror(d);a._flashOn&&(a._flashOn=!1,a.turnFlashOn().catch(()=>{}))}catch(b){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=()=>{if(b.$video.srcObject instanceof MediaStream){for(const e of b.$video.srcObject.getTracks())e.stop(),b.$video.srcObject.removeTrack(e);b.$video.srcObject=null}};if(a)return d(),!0;yield new Promise(e=>setTimeout(e,300));if(!b._paused)return!1;d();return!0})}setCamera(a){const b=this;return c.asyncExecutePromiseGeneratorFunction(function*(){a!==b._preferredCamera&&(b._preferredCamera=a,yield b._restartVideoStream())})}static scanImage(a,b,d,e,f=
!1,h=!1){return c.asyncExecutePromiseGeneratorFunction(function*(){let k=!!d;try{let m,u;[d,m]=yield Promise.all([d||g.createQrEngine(),g._loadImage(a)]);[e,u]=g._drawToCanvas(m,b,e,f);if(d instanceof Worker){let l=d;k||l.postMessage({type:"inversionMode",data:"both"});return yield new Promise((r,v)=>{let t,q,p;q=n=>{"qrResult"===n.data.type&&(l.removeEventListener("message",q),l.removeEventListener("error",p),clearTimeout(t),null!==n.data.data?r(n.data.data):v(g.NO_QR_CODE_FOUND))};p=n=>{l.removeEventListener("message",
q);l.removeEventListener("error",p);clearTimeout(t);v("Scanner error: "+(n?n.message||n:"Unknown Error"))};l.addEventListener("message",q);l.addEventListener("error",p);t=setTimeout(()=>p("timeout"),1E4);let w=u.getImageData(0,0,e.width,e.height);l.postMessage({type:"decode",data:w},[w.data.buffer])})}return yield Promise.race([new Promise((l,r)=>window.setTimeout(()=>r("Scanner error: timeout"),1E4)),(()=>c.asyncExecutePromiseGeneratorFunction(function*(){try{let [l]=yield d.detect(e);if(!l)throw g.NO_QR_CODE_FOUND;
return l.rawValue}catch(l){throw`Scanner error: ${l.message||l}`;}}))()])}catch(m){if(!b||!h)throw m;return yield g.scanImage(a,null,d,e,f)}finally{k||g._postWorkerMessage(d,"close")}})}setGrayscaleWeights(a,b,d,e=!0){g._postWorkerMessage(this._qrEnginePromise,"grayscaleWeights",{red:a,green:b,blue:d,useIntegerApproximation:e})}setInversionMode(a){g._postWorkerMessage(this._qrEnginePromise,"inversionMode",a)}static createQrEngine(a=g.WORKER_PATH){return c.asyncExecutePromiseGeneratorFunction(function*(){return"BarcodeDetector"in
window&&BarcodeDetector.getSupportedFormats&&(yield BarcodeDetector.getSupportedFormats()).includes("qr_code")?new BarcodeDetector({formats:["qr_code"]}):new Worker(a)})}_onPlay(){this._scanRegion=this._calculateScanRegion(this.$video);this._scanFrame()}_onLoadedMetaData(){this._scanRegion=this._calculateScanRegion(this.$video)}_onVisibilityChange(){document.hidden?this.pause():this._active&&this.start()}_calculateScanRegion(a){let b=Math.round(2/3*Math.min(a.videoWidth,a.videoHeight));return{x:Math.round((a.videoWidth-
b)/2),y:Math.round((a.videoHeight-b)/2),width:b,height:b,downScaledWidth:this._legacyCanvasSize,downScaledHeight:this._legacyCanvasSize}}_scanFrame(){!this._active||this.$video.paused||this.$video.ended||requestAnimationFrame(()=>{const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){if(!(1>=a.$video.readyState)){try{var b=yield g.scanImage(a.$video,a._scanRegion,a._qrEnginePromise,a.$canvas)}catch(d){if(!a._active)return;(d.message||d).includes("service unavailable")&&(a._qrEnginePromise=
g.createQrEngine());a._onDecodeError(d)}b&&a._onDecode(b)}a._scanFrame()})})}_onDecodeError(a){a!==g.NO_QR_CODE_FOUND&&console.log(a)}_getCameraStream(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){if(!navigator.mediaDevices)throw"Camera not found.";let b=/^(environment|user)$/.test(a._preferredCamera)?"facingMode":"deviceId",d=[{width:{min:1024}},{width:{min:768}},{}],e=d.map(f=>Object.assign({},f,{[b]:{exact:a._preferredCamera}}));for(let f of[...e,...d])try{let h=yield navigator.mediaDevices.getUserMedia({video:f,
audio:!1}),k=a._getFacingMode(h)||(f.facingMode?a._preferredCamera:"environment"===a._preferredCamera?"user":"environment");return{stream:h,facingMode:k}}catch(h){}throw"Camera not found.";})}_restartVideoStream(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){let b=a._paused;(yield a.pause(!0))&&!b&&a._active&&(yield a.start())})}_setVideoMirror(a){this.$video.style.transform="scaleX("+("user"===a?-1:1)+")"}_getFacingMode(a){return(a=a.getVideoTracks()[0])?/rear|back|environment/i.test(a.label)?
"environment":/front|user|face/i.test(a.label)?"user":null:null}static _drawToCanvas(a,b,d,e=!1){d=d||document.createElement("canvas");let f=b&&b.x?b.x:0,h=b&&b.y?b.y:0,k=b&&b.width?b.width:a.videoWidth||a.width,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,h,k,m,0,0,d.width,d.height);
return[d,b]}static _loadImage(a){return c.asyncExecutePromiseGeneratorFunction(function*(){if(a instanceof Image)return yield g._awaitImageLoad(a),a;if(a instanceof HTMLVideoElement||a instanceof HTMLCanvasElement||a instanceof SVGImageElement||"OffscreenCanvas"in window&&a instanceof OffscreenCanvas||"ImageBitmap"in window&&a instanceof ImageBitmap)return a;if(a instanceof File||a instanceof Blob||a instanceof URL||"string"===typeof a){let b=new Image;b.src=a instanceof File||a instanceof Blob?URL.createObjectURL(a):
a.toString();try{return yield g._awaitImageLoad(b),b}finally{(a instanceof File||a instanceof Blob)&&URL.revokeObjectURL(b.src)}}else throw"Unsupported image type.";})}static _awaitImageLoad(a){return c.asyncExecutePromiseGeneratorFunction(function*(){a.complete&&0!==a.naturalWidth||(yield new Promise((b,d)=>{let e=f=>{a.removeEventListener("load",e);a.removeEventListener("error",e);f instanceof ErrorEvent?d("Image load error"):b()};a.addEventListener("load",e);a.addEventListener("error",e)}))})}static _postWorkerMessage(a,
b,d){return c.asyncExecutePromiseGeneratorFunction(function*(){let e=yield a;e instanceof Worker&&e.postMessage({type:b,data:d})})}}g.DEFAULT_CANVAS_SIZE=400;g.NO_QR_CODE_FOUND="No QR code found";g.WORKER_PATH="qr-scanner-worker.min.js";export default g
this._onVisibilityChange);this._qrEnginePromise=g.createQrEngine()}static hasCamera(){return c.asyncExecutePromiseGeneratorFunction(function*(){try{return!!(yield g.listCameras(!1)).length}catch(a){return!1}})}static listCameras(a=!1){return c.asyncExecutePromiseGeneratorFunction(function*(){if(!navigator.mediaDevices)return[];let b=()=>c.asyncExecutePromiseGeneratorFunction(function*(){return(yield navigator.mediaDevices.enumerateDevices()).filter(e=>"videoinput"===e.kind)}),d;try{a&&(yield b()).every(e=>
!e.label)&&(d=yield navigator.mediaDevices.getUserMedia({audio:!1,video:!0}))}catch(e){}try{return(yield b()).map((e,f)=>({id:e.deviceId,label:e.label||(0===f?"Default Camera":`Camera ${f+1}`)}))}finally{if(d){console.warn("Call listCameras after successfully starting a QR scanner to avoid creating a temporary video stream");for(let e of d.getTracks())e.stop(),d.removeTrack(e)}}})}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{if(b&&b!==a.$video.srcObject){console.warn("Call hasFlash after successfully starting the scanner to avoid creating a temporary video stream");for(let d of b.getTracks())d.stop(),b.removeTrack(d)}}})}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._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.stop();g._postWorkerMessage(this._qrEnginePromise,"close")}start(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){if(!a._active||a._paused)if("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)a.$video.play();
else try{let {stream:b,facingMode:d}=yield a._getCameraStream();a.$video.srcObject=b;a.$video.play();a._setVideoMirror(d);a._flashOn&&(a._flashOn=!1,a.turnFlashOn().catch(()=>{}))}catch(b){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=()=>{if(b.$video.srcObject instanceof MediaStream){for(const e of b.$video.srcObject.getTracks())e.stop(),b.$video.srcObject.removeTrack(e);
b.$video.srcObject=null}};if(a)return d(),!0;yield new Promise(e=>setTimeout(e,300));if(!b._paused)return!1;d();return!0})}setCamera(a){const b=this;return c.asyncExecutePromiseGeneratorFunction(function*(){a!==b._preferredCamera&&(b._preferredCamera=a,yield b._restartVideoStream())})}static scanImage(a,b,d,e,f=!1,h=!1){return c.asyncExecutePromiseGeneratorFunction(function*(){let k=!!d;try{let m,u;[d,m]=yield Promise.all([d||g.createQrEngine(),g._loadImage(a)]);[e,u]=g._drawToCanvas(m,b,e,f);if(d instanceof
Worker){let l=d;k||l.postMessage({type:"inversionMode",data:"both"});return yield new Promise((r,v)=>{let t,q,p;q=n=>{"qrResult"===n.data.type&&(l.removeEventListener("message",q),l.removeEventListener("error",p),clearTimeout(t),null!==n.data.data?r(n.data.data):v(g.NO_QR_CODE_FOUND))};p=n=>{l.removeEventListener("message",q);l.removeEventListener("error",p);clearTimeout(t);v("Scanner error: "+(n?n.message||n:"Unknown Error"))};l.addEventListener("message",q);l.addEventListener("error",p);t=setTimeout(()=>
p("timeout"),1E4);let w=u.getImageData(0,0,e.width,e.height);l.postMessage({type:"decode",data:w},[w.data.buffer])})}return yield Promise.race([new Promise((l,r)=>window.setTimeout(()=>r("Scanner error: timeout"),1E4)),(()=>c.asyncExecutePromiseGeneratorFunction(function*(){try{let [l]=yield d.detect(e);if(!l)throw g.NO_QR_CODE_FOUND;return l.rawValue}catch(l){throw`Scanner error: ${l.message||l}`;}}))()])}catch(m){if(!b||!h)throw m;return yield g.scanImage(a,null,d,e,f)}finally{k||g._postWorkerMessage(d,
"close")}})}setGrayscaleWeights(a,b,d,e=!0){g._postWorkerMessage(this._qrEnginePromise,"grayscaleWeights",{red:a,green:b,blue:d,useIntegerApproximation:e})}setInversionMode(a){g._postWorkerMessage(this._qrEnginePromise,"inversionMode",a)}static createQrEngine(a=g.WORKER_PATH){return c.asyncExecutePromiseGeneratorFunction(function*(){return"BarcodeDetector"in window&&BarcodeDetector.getSupportedFormats&&(yield BarcodeDetector.getSupportedFormats()).includes("qr_code")?new BarcodeDetector({formats:["qr_code"]}):
new Worker(a)})}_onPlay(){this._scanRegion=this._calculateScanRegion(this.$video);this._scanFrame()}_onLoadedMetaData(){this._scanRegion=this._calculateScanRegion(this.$video)}_onVisibilityChange(){document.hidden?this.pause():this._active&&this.start()}_calculateScanRegion(a){let b=Math.round(2/3*Math.min(a.videoWidth,a.videoHeight));return{x:Math.round((a.videoWidth-b)/2),y:Math.round((a.videoHeight-b)/2),width:b,height:b,downScaledWidth:this._legacyCanvasSize,downScaledHeight:this._legacyCanvasSize}}_scanFrame(){!this._active||
this.$video.paused||this.$video.ended||requestAnimationFrame(()=>{const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){if(!(1>=a.$video.readyState)){try{var b=yield g.scanImage(a.$video,a._scanRegion,a._qrEnginePromise,a.$canvas)}catch(d){if(!a._active)return;(d.message||d).includes("service unavailable")&&(a._qrEnginePromise=g.createQrEngine());a._onDecodeError(d)}b&&a._onDecode(b)}a._scanFrame()})})}_onDecodeError(a){a!==g.NO_QR_CODE_FOUND&&console.log(a)}_getCameraStream(){const a=
this;return c.asyncExecutePromiseGeneratorFunction(function*(){if(!navigator.mediaDevices)throw"Camera not found.";let b=/^(environment|user)$/.test(a._preferredCamera)?"facingMode":"deviceId",d=[{width:{min:1024}},{width:{min:768}},{}],e=d.map(f=>Object.assign({},f,{[b]:{exact:a._preferredCamera}}));for(let f of[...e,...d])try{let h=yield navigator.mediaDevices.getUserMedia({video:f,audio:!1}),k=a._getFacingMode(h)||(f.facingMode?a._preferredCamera:"environment"===a._preferredCamera?"user":"environment");
return{stream:h,facingMode:k}}catch(h){}throw"Camera not found.";})}_restartVideoStream(){const a=this;return c.asyncExecutePromiseGeneratorFunction(function*(){let b=a._paused;(yield a.pause(!0))&&!b&&a._active&&(yield a.start())})}_setVideoMirror(a){this.$video.style.transform="scaleX("+("user"===a?-1:1)+")"}_getFacingMode(a){return(a=a.getVideoTracks()[0])?/rear|back|environment/i.test(a.label)?"environment":/front|user|face/i.test(a.label)?"user":null:null}static _drawToCanvas(a,b,d,e=!1){d=d||
document.createElement("canvas");let f=b&&b.x?b.x:0,h=b&&b.y?b.y:0,k=b&&b.width?b.width:a.videoWidth||a.width,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,h,k,m,0,0,d.width,d.height);return[d,b]}static _loadImage(a){return c.asyncExecutePromiseGeneratorFunction(function*(){if(a instanceof
Image)return yield g._awaitImageLoad(a),a;if(a instanceof HTMLVideoElement||a instanceof HTMLCanvasElement||a instanceof SVGImageElement||"OffscreenCanvas"in window&&a instanceof OffscreenCanvas||"ImageBitmap"in window&&a instanceof ImageBitmap)return a;if(a instanceof File||a instanceof Blob||a instanceof URL||"string"===typeof a){let b=new Image;b.src=a instanceof File||a instanceof Blob?URL.createObjectURL(a):a.toString();try{return yield g._awaitImageLoad(b),b}finally{(a instanceof File||a instanceof
Blob)&&URL.revokeObjectURL(b.src)}}else throw"Unsupported image type.";})}static _awaitImageLoad(a){return c.asyncExecutePromiseGeneratorFunction(function*(){a.complete&&0!==a.naturalWidth||(yield new Promise((b,d)=>{let e=f=>{a.removeEventListener("load",e);a.removeEventListener("error",e);f instanceof ErrorEvent?d("Image load error"):b()};a.addEventListener("load",e);a.addEventListener("error",e)}))})}static _postWorkerMessage(a,b,d){return c.asyncExecutePromiseGeneratorFunction(function*(){let e=
yield a;e instanceof Worker&&e.postMessage({type:b,data:d})})}}g.DEFAULT_CANVAS_SIZE=400;g.NO_QR_CODE_FOUND="No QR code found";g.WORKER_PATH="qr-scanner-worker.min.js";export default g
//# 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":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
+17 -17
View File
@@ -1,20 +1,20 @@
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;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?
class e{constructor(a,b,c,d,f="environment"){this._legacyCanvasSize=e.DEFAULT_CANVAS_SIZE;this._flashOn=this._paused=this._active=!1;c=c||this._onDecodeError;d=d||this._calculateScanRegion;this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;this._preferredCamera=f;"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{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{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=()=>{if(this.$video.srcObject instanceof MediaStream){for(const c of this.$video.srcObject.getTracks())c.stop(),this.$video.srcObject.removeTrack(c);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
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{if(c){console.warn("Call listCameras after successfully starting a QR scanner to avoid creating a temporary video stream");for(let d of c.getTracks())d.stop(),c.removeTrack(d)}}}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{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=()=>{if(this.$video.srcObject instanceof MediaStream){for(const c of this.$video.srcObject.getTracks())c.stop(),
this.$video.srcObject.removeTrack(c);this.$video.srcObject=null}};if(a)return b(),!0;await new Promise(c=>setTimeout(c,300));if(!this._paused)return!1;b();return!0}async setCamera(a){a!==this._preferredCamera&&(this._preferredCamera=a,await this._restartVideoStream())}static async scanImage(a,b,c,d,f=!1,h=!1){let k=!!c;try{let l,t;[c,l]=await Promise.all([c||e.createQrEngine(),e._loadImage(a)]);[d,t]=e._drawToCanvas(l,b,d,f);if(c instanceof Worker){let g=c;k||g.postMessage({type:"inversionMode",data:"both"});
return await new Promise((q,u)=>{let r,p,n;p=m=>{"qrResult"===m.data.type&&(g.removeEventListener("message",p),g.removeEventListener("error",n),clearTimeout(r),null!==m.data.data?q(m.data.data):u(e.NO_QR_CODE_FOUND))};n=m=>{g.removeEventListener("message",p);g.removeEventListener("error",n);clearTimeout(r);u("Scanner error: "+(m?m.message||m:"Unknown Error"))};g.addEventListener("message",p);g.addEventListener("error",n);r=setTimeout(()=>n("timeout"),1E4);let v=t.getImageData(0,0,d.width,d.height);
g.postMessage({type:"decode",data:v},[v.data.buffer])})}return await Promise.race([new Promise((g,q)=>window.setTimeout(()=>q("Scanner error: timeout"),1E4)),(async()=>{try{let [g]=await c.detect(d);if(!g)throw e.NO_QR_CODE_FOUND;return g.rawValue}catch(g){throw`Scanner error: ${g.message||g}`;}})()])}catch(l){if(!b||!h)throw l;return await e.scanImage(a,null,c,d,f)}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 f=await navigator.mediaDevices.getUserMedia({video:d,audio:!1}),h=this._getFacingMode(f)||(d.facingMode?this._preferredCamera:"environment"===this._preferredCamera?"user":"environment");return{stream:f,facingMode:h}}catch(f){}throw"Camera not found.";}async _restartVideoStream(){let a=this._paused;await this.pause(!0)&&!a&&this._active&&await this.start()}_setVideoMirror(a){this.$video.style.transform="scaleX("+("user"===a?-1:1)+")"}_getFacingMode(a){return(a=a.getVideoTracks()[0])?
/rear|back|environment/i.test(a.label)?"environment":/front|user|face/i.test(a.label)?"user":null:null}static _drawToCanvas(a,b,c,d=!1){c=c||document.createElement("canvas");let f=b&&b.x?b.x:0,h=b&&b.y?b.y:0,k=b&&b.width?b.width:a.videoWidth||a.width,l=b&&b.height?b.height:a.videoHeight||a.height;d||(d=b&&b.downScaledWidth?b.downScaledWidth:k,b=b&&b.downScaledHeight?b.downScaledHeight:l,c.width!==d&&(c.width=d),c.height!==b&&(c.height=b));b=c.getContext("2d",{alpha:!1});b.imageSmoothingEnabled=!1;
b.drawImage(a,f,h,k,l,0,0,c.width,c.height);return[c,b]}static async _loadImage(a){if(a instanceof Image)return await e._awaitImageLoad(a),a;if(a instanceof HTMLVideoElement||a instanceof HTMLCanvasElement||a instanceof SVGImageElement||"OffscreenCanvas"in window&&a instanceof OffscreenCanvas||"ImageBitmap"in window&&a instanceof ImageBitmap)return a;if(a instanceof File||a instanceof Blob||a instanceof URL||"string"===typeof a){let b=new Image;b.src=a instanceof File||a instanceof Blob?URL.createObjectURL(a):
a.toString();try{return await e._awaitImageLoad(b),b}finally{(a instanceof File||a instanceof Blob)&&URL.revokeObjectURL(b.src)}}else throw"Unsupported image type.";}static async _awaitImageLoad(a){a.complete&&0!==a.naturalWidth||await new Promise((b,c)=>{let d=f=>{a.removeEventListener("load",d);a.removeEventListener("error",d);f instanceof ErrorEvent?c("Image load error"):b()};a.addEventListener("load",d);a.addEventListener("error",d)})}static async _postWorkerMessage(a,b,c){a=await a;a instanceof
Worker&&a.postMessage({type:b,data:c})}}e.DEFAULT_CANVAS_SIZE=400;e.NO_QR_CODE_FOUND="No QR code found";e.WORKER_PATH="qr-scanner-worker.min.js";export default e
//# sourceMappingURL=qr-scanner.min.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"qr-scanner.min.js","sources":["src/qr-scanner.ts"],"sourcesContent":["export default class QrScanner {\n static readonly DEFAULT_CANVAS_SIZE = 400;\n static readonly NO_QR_CODE_FOUND = 'No QR code found';\n static WORKER_PATH = 'qr-scanner-worker.min.js';\n\n static async hasCamera(): Promise<boolean> {\n try {\n return !!(await QrScanner.listCameras(false)).length;\n } catch (e) {\n return false;\n }\n }\n\n static async listCameras(requestLabels = false): Promise<Array<QrScanner.Camera>> {\n if (!navigator.mediaDevices) return [];\n\n // Note that enumerateDevices can always be called and does not prompt the user for permission.\n // However, enumerateDevices only includes device labels if served via https and an active media stream exists\n // or permission to access the camera was given. Therefore, ask for camera permission by opening a stream, if\n // labels were requested.\n let openedStream: MediaStream | undefined;\n if (requestLabels) {\n try {\n openedStream = await navigator.mediaDevices.getUserMedia({ audio: false, video: true });\n } catch (e) {\n // Fail gracefully, especially if the device has no camera or on mobile when the camera is already in\n // use and some browsers disallow a second stream.\n }\n }\n try {\n const devices = await navigator.mediaDevices.enumerateDevices();\n return devices.filter(device => device.kind === 'videoinput').map((device, i) => ({\n id: device.deviceId,\n label: device.label || (i === 0 ? 'Default Camera' : `Camera ${i + 1}`),\n }));\n } finally {\n // close the stream we just opened for getting camera access for listing the device labels\n if (openedStream) {\n for (const track of openedStream.getTracks()) {\n track.stop();\n openedStream.removeTrack(track);\n }\n }\n }\n }\n\n $video: HTMLVideoElement;\n $canvas: HTMLCanvasElement;\n private readonly _onDecode: (result: string) => void;\n private _preferredCamera: QrScanner.FacingMode | QrScanner.DeviceId;\n private _scanRegion: QrScanner.ScanRegion;\n private _legacyCanvasSize: number = QrScanner.DEFAULT_CANVAS_SIZE;\n private _qrEnginePromise: Promise<Worker | BarcodeDetector>\n private _active: boolean = false;\n private _paused: boolean = false;\n private _flashOn: boolean = false;\n\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: string) => void,\n onDecodeError?: (error: Error | string) => void,\n calculateScanRegion?: (video: HTMLVideoElement) => QrScanner.ScanRegion,\n preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,\n );\n /** @deprecated */\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: string) => void,\n onDecodeError?: (error: Error | string) => void,\n canvasSize?: number,\n preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,\n );\n /** @deprecated */\n constructor(video: HTMLVideoElement, onDecode: (result: string) => void, canvasSize?: number);\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: string) => void,\n canvasSizeOrOnDecodeError?: number | ((error: Error | string) => void),\n canvasSizeOrCalculateScanRegion?: number | ((video: HTMLVideoElement) => QrScanner.ScanRegion),\n preferredCamera: QrScanner.FacingMode | QrScanner.DeviceId = 'environment',\n ) {\n canvasSizeOrOnDecodeError = canvasSizeOrOnDecodeError || this._onDecodeError;\n canvasSizeOrCalculateScanRegion = canvasSizeOrCalculateScanRegion || this._calculateScanRegion;\n\n this.$video = video;\n this.$canvas = document.createElement('canvas');\n this._onDecode = onDecode;\n this._preferredCamera = preferredCamera;\n\n if (typeof canvasSizeOrOnDecodeError === 'number') {\n // legacy function signature where the third argument is the canvas size\n this._legacyCanvasSize = canvasSizeOrOnDecodeError;\n console.warn('You\\'re using a deprecated version of the QrScanner constructor which will be removed in '\n + 'the future');\n } else {\n this._onDecodeError = canvasSizeOrOnDecodeError;\n }\n\n if (typeof canvasSizeOrCalculateScanRegion === 'number') {\n // legacy function signature where the fourth argument is the canvas size\n this._legacyCanvasSize = canvasSizeOrCalculateScanRegion;\n console.warn('You\\'re using a deprecated version of the QrScanner constructor which will be removed in '\n + 'the future');\n } else {\n Line truncated
{"version":3,"file":"qr-scanner.min.js","sources":["src/qr-scanner.ts"],"sourcesContent":["export default class QrScanner {\n static readonly DEFAULT_CANVAS_SIZE = 400;\n static readonly NO_QR_CODE_FOUND = 'No QR code found';\n static WORKER_PATH = 'qr-scanner-worker.min.js';\n\n static async hasCamera(): Promise<boolean> {\n try {\n return !!(await QrScanner.listCameras(false)).length;\n } catch (e) {\n return false;\n }\n }\n\n static async listCameras(requestLabels = false): Promise<Array<QrScanner.Camera>> {\n if (!navigator.mediaDevices) return [];\n\n const enumerateCameras = async (): Promise<Array<MediaDeviceInfo>> =>\n (await navigator.mediaDevices.enumerateDevices()).filter((device) => device.kind === 'videoinput');\n\n // Note that enumerateDevices can always be called and does not prompt the user for permission.\n // However, enumerateDevices only includes device labels if served via https and an active media stream exists\n // or permission to access the camera was given. Therefore, if we're not getting labels but labels are requested\n // ask for camera permission by opening a stream.\n let openedStream: MediaStream | undefined;\n try {\n if (requestLabels && (await enumerateCameras()).every((camera) => !camera.label)) {\n openedStream = await navigator.mediaDevices.getUserMedia({ audio: false, video: true });\n }\n } catch (e) {\n // Fail gracefully, especially if the device has no camera or on mobile when the camera is already in use\n // and some browsers disallow a second stream.\n }\n\n try {\n return (await enumerateCameras()).map((camera, i) => ({\n id: camera.deviceId,\n label: camera.label || (i === 0 ? 'Default Camera' : `Camera ${i + 1}`),\n }));\n } finally {\n // close the stream we just opened for getting camera access for listing the device labels\n if (openedStream) {\n console.warn('Call listCameras after successfully starting a QR scanner to avoid creating '\n + 'a temporary video stream');\n for (const track of openedStream.getTracks()) {\n track.stop();\n openedStream.removeTrack(track);\n }\n }\n }\n }\n\n $video: HTMLVideoElement;\n $canvas: HTMLCanvasElement;\n private readonly _onDecode: (result: string) => void;\n private _preferredCamera: QrScanner.FacingMode | QrScanner.DeviceId;\n private _scanRegion: QrScanner.ScanRegion;\n private _legacyCanvasSize: number = QrScanner.DEFAULT_CANVAS_SIZE;\n private _qrEnginePromise: Promise<Worker | BarcodeDetector>\n private _active: boolean = false;\n private _paused: boolean = false;\n private _flashOn: boolean = false;\n\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: string) => void,\n onDecodeError?: (error: Error | string) => void,\n calculateScanRegion?: (video: HTMLVideoElement) => QrScanner.ScanRegion,\n preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,\n );\n /** @deprecated */\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: string) => void,\n onDecodeError?: (error: Error | string) => void,\n canvasSize?: number,\n preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,\n );\n /** @deprecated */\n constructor(video: HTMLVideoElement, onDecode: (result: string) => void, canvasSize?: number);\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: string) => void,\n canvasSizeOrOnDecodeError?: number | ((error: Error | string) => void),\n canvasSizeOrCalculateScanRegion?: number | ((video: HTMLVideoElement) => QrScanner.ScanRegion),\n preferredCamera: QrScanner.FacingMode | QrScanner.DeviceId = 'environment',\n ) {\n canvasSizeOrOnDecodeError = canvasSizeOrOnDecodeError || this._onDecodeError;\n canvasSizeOrCalculateScanRegion = canvasSizeOrCalculateScanRegion || this._calculateScanRegion;\n\n this.$video = video;\n this.$canvas = document.createElement('canvas');\n this._onDecode = onDecode;\n this._preferredCamera = preferredCamera;\n\n if (typeof canvasSizeOrOnDecodeError === 'number') {\n // legacy function signature where the third argument is the canvas size\n this._legacyCanvasSize = canvasSizeOrOnDecodeError;\n console.warn('You\\'re using a deprecated version of the QrScanner constructor which will be removed in '\n + 'the future');\n } else {\n this._onDecodeError = canvasSizeOrOnDecodeError;\n }\n\n if (typeof canvasSizeOrCalculateScanRegion === 'number') {\n Line truncated
+18 -18
View File
@@ -1,20 +1,20 @@
'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,g="environment"){this._legacyCanvasSize=e.DEFAULT_CANVAS_SIZE;this._flashOn=this._paused=this._active=!1;c=c||this._onDecodeError;d=d||this._calculateScanRegion;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=
'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="environment"){this._legacyCanvasSize=e.DEFAULT_CANVAS_SIZE;this._flashOn=this._paused=this._active=!1;c=c||this._onDecodeError;d=d||this._calculateScanRegion;this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=
b;this._preferredCamera=f;"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{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{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=()=>{if(this.$video.srcObject instanceof MediaStream){for(const c of this.$video.srcObject.getTracks())c.stop(),this.$video.srcObject.removeTrack(c);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";return e})
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{if(c){console.warn("Call listCameras after successfully starting a QR scanner to avoid creating a temporary video stream");for(let d of c.getTracks())d.stop(),c.removeTrack(d)}}}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{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=()=>{if(this.$video.srcObject instanceof MediaStream){for(const c of this.$video.srcObject.getTracks())c.stop(),this.$video.srcObject.removeTrack(c);this.$video.srcObject=null}};if(a)return b(),!0;await new Promise(c=>setTimeout(c,300));if(!this._paused)return!1;b();return!0}async setCamera(a){a!==
this._preferredCamera&&(this._preferredCamera=a,await this._restartVideoStream())}static async scanImage(a,b,c,d,f=!1,h=!1){let k=!!c;try{let l,t;[c,l]=await Promise.all([c||e.createQrEngine(),e._loadImage(a)]);[d,t]=e._drawToCanvas(l,b,d,f);if(c instanceof Worker){let g=c;k||g.postMessage({type:"inversionMode",data:"both"});return await new Promise((q,u)=>{let r,p,n;p=m=>{"qrResult"===m.data.type&&(g.removeEventListener("message",p),g.removeEventListener("error",n),clearTimeout(r),null!==m.data.data?
q(m.data.data):u(e.NO_QR_CODE_FOUND))};n=m=>{g.removeEventListener("message",p);g.removeEventListener("error",n);clearTimeout(r);u("Scanner error: "+(m?m.message||m:"Unknown Error"))};g.addEventListener("message",p);g.addEventListener("error",n);r=setTimeout(()=>n("timeout"),1E4);let v=t.getImageData(0,0,d.width,d.height);g.postMessage({type:"decode",data:v},[v.data.buffer])})}return await Promise.race([new Promise((g,q)=>window.setTimeout(()=>q("Scanner error: timeout"),1E4)),(async()=>{try{let [g]=
await c.detect(d);if(!g)throw e.NO_QR_CODE_FOUND;return g.rawValue}catch(g){throw`Scanner error: ${g.message||g}`;}})()])}catch(l){if(!b||!h)throw l;return await e.scanImage(a,null,c,d,f)}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 f=await navigator.mediaDevices.getUserMedia({video:d,audio:!1}),h=this._getFacingMode(f)||(d.facingMode?
this._preferredCamera:"environment"===this._preferredCamera?"user":"environment");return{stream:f,facingMode:h}}catch(f){}throw"Camera not found.";}async _restartVideoStream(){let a=this._paused;await this.pause(!0)&&!a&&this._active&&await this.start()}_setVideoMirror(a){this.$video.style.transform="scaleX("+("user"===a?-1:1)+")"}_getFacingMode(a){return(a=a.getVideoTracks()[0])?/rear|back|environment/i.test(a.label)?"environment":/front|user|face/i.test(a.label)?"user":null:null}static _drawToCanvas(a,
b,c,d=!1){c=c||document.createElement("canvas");let f=b&&b.x?b.x:0,h=b&&b.y?b.y:0,k=b&&b.width?b.width:a.videoWidth||a.width,l=b&&b.height?b.height:a.videoHeight||a.height;d||(d=b&&b.downScaledWidth?b.downScaledWidth:k,b=b&&b.downScaledHeight?b.downScaledHeight:l,c.width!==d&&(c.width=d),c.height!==b&&(c.height=b));b=c.getContext("2d",{alpha:!1});b.imageSmoothingEnabled=!1;b.drawImage(a,f,h,k,l,0,0,c.width,c.height);return[c,b]}static async _loadImage(a){if(a instanceof Image)return await e._awaitImageLoad(a),
a;if(a instanceof HTMLVideoElement||a instanceof HTMLCanvasElement||a instanceof SVGImageElement||"OffscreenCanvas"in window&&a instanceof OffscreenCanvas||"ImageBitmap"in window&&a instanceof ImageBitmap)return a;if(a instanceof File||a instanceof Blob||a instanceof URL||"string"===typeof a){let b=new Image;b.src=a instanceof File||a instanceof Blob?URL.createObjectURL(a):a.toString();try{return await e._awaitImageLoad(b),b}finally{(a instanceof File||a instanceof Blob)&&URL.revokeObjectURL(b.src)}}else throw"Unsupported image type.";
}static async _awaitImageLoad(a){a.complete&&0!==a.naturalWidth||await new Promise((b,c)=>{let d=f=>{a.removeEventListener("load",d);a.removeEventListener("error",d);f instanceof ErrorEvent?c("Image load error"):b()};a.addEventListener("load",d);a.addEventListener("error",d)})}static async _postWorkerMessage(a,b,c){a=await a;a instanceof Worker&&a.postMessage({type:b,data:c})}}e.DEFAULT_CANVAS_SIZE=400;e.NO_QR_CODE_FOUND="No QR code found";e.WORKER_PATH="qr-scanner-worker.min.js";return e})
//# sourceMappingURL=qr-scanner.umd.min.js.map
+1 -1
View File
@@ -1 +1 @@
{"version":3,"file":"qr-scanner.umd.min.js","sources":["src/qr-scanner.ts"],"sourcesContent":["export default class QrScanner {\n static readonly DEFAULT_CANVAS_SIZE = 400;\n static readonly NO_QR_CODE_FOUND = 'No QR code found';\n static WORKER_PATH = 'qr-scanner-worker.min.js';\n\n static async hasCamera(): Promise<boolean> {\n try {\n return !!(await QrScanner.listCameras(false)).length;\n } catch (e) {\n return false;\n }\n }\n\n static async listCameras(requestLabels = false): Promise<Array<QrScanner.Camera>> {\n if (!navigator.mediaDevices) return [];\n\n // Note that enumerateDevices can always be called and does not prompt the user for permission.\n // However, enumerateDevices only includes device labels if served via https and an active media stream exists\n // or permission to access the camera was given. Therefore, ask for camera permission by opening a stream, if\n // labels were requested.\n let openedStream: MediaStream | undefined;\n if (requestLabels) {\n try {\n openedStream = await navigator.mediaDevices.getUserMedia({ audio: false, video: true });\n } catch (e) {\n // Fail gracefully, especially if the device has no camera or on mobile when the camera is already in\n // use and some browsers disallow a second stream.\n }\n }\n try {\n const devices = await navigator.mediaDevices.enumerateDevices();\n return devices.filter(device => device.kind === 'videoinput').map((device, i) => ({\n id: device.deviceId,\n label: device.label || (i === 0 ? 'Default Camera' : `Camera ${i + 1}`),\n }));\n } finally {\n // close the stream we just opened for getting camera access for listing the device labels\n if (openedStream) {\n for (const track of openedStream.getTracks()) {\n track.stop();\n openedStream.removeTrack(track);\n }\n }\n }\n }\n\n $video: HTMLVideoElement;\n $canvas: HTMLCanvasElement;\n private readonly _onDecode: (result: string) => void;\n private _preferredCamera: QrScanner.FacingMode | QrScanner.DeviceId;\n private _scanRegion: QrScanner.ScanRegion;\n private _legacyCanvasSize: number = QrScanner.DEFAULT_CANVAS_SIZE;\n private _qrEnginePromise: Promise<Worker | BarcodeDetector>\n private _active: boolean = false;\n private _paused: boolean = false;\n private _flashOn: boolean = false;\n\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: string) => void,\n onDecodeError?: (error: Error | string) => void,\n calculateScanRegion?: (video: HTMLVideoElement) => QrScanner.ScanRegion,\n preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,\n );\n /** @deprecated */\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: string) => void,\n onDecodeError?: (error: Error | string) => void,\n canvasSize?: number,\n preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,\n );\n /** @deprecated */\n constructor(video: HTMLVideoElement, onDecode: (result: string) => void, canvasSize?: number);\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: string) => void,\n canvasSizeOrOnDecodeError?: number | ((error: Error | string) => void),\n canvasSizeOrCalculateScanRegion?: number | ((video: HTMLVideoElement) => QrScanner.ScanRegion),\n preferredCamera: QrScanner.FacingMode | QrScanner.DeviceId = 'environment',\n ) {\n canvasSizeOrOnDecodeError = canvasSizeOrOnDecodeError || this._onDecodeError;\n canvasSizeOrCalculateScanRegion = canvasSizeOrCalculateScanRegion || this._calculateScanRegion;\n\n this.$video = video;\n this.$canvas = document.createElement('canvas');\n this._onDecode = onDecode;\n this._preferredCamera = preferredCamera;\n\n if (typeof canvasSizeOrOnDecodeError === 'number') {\n // legacy function signature where the third argument is the canvas size\n this._legacyCanvasSize = canvasSizeOrOnDecodeError;\n console.warn('You\\'re using a deprecated version of the QrScanner constructor which will be removed in '\n + 'the future');\n } else {\n this._onDecodeError = canvasSizeOrOnDecodeError;\n }\n\n if (typeof canvasSizeOrCalculateScanRegion === 'number') {\n // legacy function signature where the fourth argument is the canvas size\n this._legacyCanvasSize = canvasSizeOrCalculateScanRegion;\n console.warn('You\\'re using a deprecated version of the QrScanner constructor which will be removed in '\n + 'the future');\n } else {\n Line truncated
{"version":3,"file":"qr-scanner.umd.min.js","sources":["src/qr-scanner.ts"],"sourcesContent":["export default class QrScanner {\n static readonly DEFAULT_CANVAS_SIZE = 400;\n static readonly NO_QR_CODE_FOUND = 'No QR code found';\n static WORKER_PATH = 'qr-scanner-worker.min.js';\n\n static async hasCamera(): Promise<boolean> {\n try {\n return !!(await QrScanner.listCameras(false)).length;\n } catch (e) {\n return false;\n }\n }\n\n static async listCameras(requestLabels = false): Promise<Array<QrScanner.Camera>> {\n if (!navigator.mediaDevices) return [];\n\n const enumerateCameras = async (): Promise<Array<MediaDeviceInfo>> =>\n (await navigator.mediaDevices.enumerateDevices()).filter((device) => device.kind === 'videoinput');\n\n // Note that enumerateDevices can always be called and does not prompt the user for permission.\n // However, enumerateDevices only includes device labels if served via https and an active media stream exists\n // or permission to access the camera was given. Therefore, if we're not getting labels but labels are requested\n // ask for camera permission by opening a stream.\n let openedStream: MediaStream | undefined;\n try {\n if (requestLabels && (await enumerateCameras()).every((camera) => !camera.label)) {\n openedStream = await navigator.mediaDevices.getUserMedia({ audio: false, video: true });\n }\n } catch (e) {\n // Fail gracefully, especially if the device has no camera or on mobile when the camera is already in use\n // and some browsers disallow a second stream.\n }\n\n try {\n return (await enumerateCameras()).map((camera, i) => ({\n id: camera.deviceId,\n label: camera.label || (i === 0 ? 'Default Camera' : `Camera ${i + 1}`),\n }));\n } finally {\n // close the stream we just opened for getting camera access for listing the device labels\n if (openedStream) {\n console.warn('Call listCameras after successfully starting a QR scanner to avoid creating '\n + 'a temporary video stream');\n for (const track of openedStream.getTracks()) {\n track.stop();\n openedStream.removeTrack(track);\n }\n }\n }\n }\n\n $video: HTMLVideoElement;\n $canvas: HTMLCanvasElement;\n private readonly _onDecode: (result: string) => void;\n private _preferredCamera: QrScanner.FacingMode | QrScanner.DeviceId;\n private _scanRegion: QrScanner.ScanRegion;\n private _legacyCanvasSize: number = QrScanner.DEFAULT_CANVAS_SIZE;\n private _qrEnginePromise: Promise<Worker | BarcodeDetector>\n private _active: boolean = false;\n private _paused: boolean = false;\n private _flashOn: boolean = false;\n\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: string) => void,\n onDecodeError?: (error: Error | string) => void,\n calculateScanRegion?: (video: HTMLVideoElement) => QrScanner.ScanRegion,\n preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,\n );\n /** @deprecated */\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: string) => void,\n onDecodeError?: (error: Error | string) => void,\n canvasSize?: number,\n preferredCamera?: QrScanner.FacingMode | QrScanner.DeviceId,\n );\n /** @deprecated */\n constructor(video: HTMLVideoElement, onDecode: (result: string) => void, canvasSize?: number);\n constructor(\n video: HTMLVideoElement,\n onDecode: (result: string) => void,\n canvasSizeOrOnDecodeError?: number | ((error: Error | string) => void),\n canvasSizeOrCalculateScanRegion?: number | ((video: HTMLVideoElement) => QrScanner.ScanRegion),\n preferredCamera: QrScanner.FacingMode | QrScanner.DeviceId = 'environment',\n ) {\n canvasSizeOrOnDecodeError = canvasSizeOrOnDecodeError || this._onDecodeError;\n canvasSizeOrCalculateScanRegion = canvasSizeOrCalculateScanRegion || this._calculateScanRegion;\n\n this.$video = video;\n this.$canvas = document.createElement('canvas');\n this._onDecode = onDecode;\n this._preferredCamera = preferredCamera;\n\n if (typeof canvasSizeOrOnDecodeError === 'number') {\n // legacy function signature where the third argument is the canvas size\n this._legacyCanvasSize = canvasSizeOrOnDecodeError;\n console.warn('You\\'re using a deprecated version of the QrScanner constructor which will be removed in '\n + 'the future');\n } else {\n this._onDecodeError = canvasSizeOrOnDecodeError;\n }\n\n if (typeof canvasSizeOrCalculateScanRegion === 'number') {\n Line truncated
+19 -14
View File
@@ -14,28 +14,33 @@ export default class QrScanner {
static async listCameras(requestLabels = false): Promise<Array<QrScanner.Camera>> {
if (!navigator.mediaDevices) return [];
const enumerateCameras = async (): Promise<Array<MediaDeviceInfo>> =>
(await navigator.mediaDevices.enumerateDevices()).filter((device) => device.kind === 'videoinput');
// 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.
// or permission to access the camera was given. Therefore, if we're not getting labels but labels are requested
// ask for camera permission by opening a stream.
let openedStream: MediaStream | undefined;
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.
}
}
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}`),
if (requestLabels && (await enumerateCameras()).every((camera) => !camera.label)) {
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.
}
try {
return (await enumerateCameras()).map((camera, i) => ({
id: camera.deviceId,
label: camera.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) {
console.warn('Call listCameras after successfully starting a QR scanner to avoid creating '
+ 'a temporary video stream');
for (const track of openedStream.getTracks()) {
track.stop();
openedStream.removeTrack(track);