mirror of
https://github.com/nimiq/qr-scanner.git
synced 2026-09-22 18:23:54 +00:00
Add option to get camera list and allow to set specific device to use
This commit is contained in:
1 parent
e8a77dea3f
commit
c53a1ecb4a
6 files changed
+111
-37
No files matched your search
@@ -46,6 +46,12 @@
|
||||
</select>
|
||||
<br>
|
||||
</div>
|
||||
<div>
|
||||
<select id="cam-list">
|
||||
<option value="">Automatic</option>
|
||||
</select>
|
||||
<br>
|
||||
</div>
|
||||
<b>Detected QR code: </b>
|
||||
<span id="cam-qr-result">None</span>
|
||||
<br>
|
||||
@@ -74,6 +80,7 @@
|
||||
const camQrResultTimestamp = document.getElementById('cam-qr-result-timestamp');
|
||||
const fileSelector = document.getElementById('file-selector');
|
||||
const fileQrResult = document.getElementById('file-qr-result');
|
||||
const camList = document.getElementById('cam-list');
|
||||
|
||||
function setResult(label, result) {
|
||||
label.textContent = result;
|
||||
@@ -87,6 +94,13 @@
|
||||
|
||||
QrScanner.hasCamera().then(hasCamera => camHasCamera.textContent = hasCamera);
|
||||
|
||||
QrScanner.getCameraList().then(cameras => cameras.forEach(camera => {
|
||||
const option = document.createElement('option');
|
||||
option.value = camera.id;
|
||||
option.text = camera.label;
|
||||
camList.add(option);
|
||||
}));
|
||||
|
||||
const scanner = new QrScanner(video, result => setResult(camQrResult, result), error => {
|
||||
camQrResult.textContent = error;
|
||||
camQrResult.style.color = 'inherit';
|
||||
@@ -117,6 +131,10 @@
|
||||
scanner.setInversionMode(event.target.value);
|
||||
});
|
||||
|
||||
document.getElementById('cam-list').addEventListener('change', event => {
|
||||
scanner.setCamera(event.target.value === '' ? null : event.target.value);
|
||||
});
|
||||
|
||||
document.getElementById('start-button').addEventListener('click', () => {
|
||||
scanner.start();
|
||||
});
|
||||
|
||||
Vendored
+17
-16
@@ -1,17 +1,18 @@
|
||||
class e{static hasCamera(){return navigator.mediaDevices?navigator.mediaDevices.enumerateDevices().then(a=>a.some(a=>"videoinput"===a.kind)).catch(()=>!1):Promise.resolve(!1)}constructor(a,b,c=this._onDecodeError,d=this._calculateScanRegion,f="environment"){this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;this._legacyCanvasSize=e.DEFAULT_CANVAS_SIZE;this._preferredFacingMode=f;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);
|
||||
this.$video.playsInline=!0;this.$video.muted=!0;this.$video.disablePictureInPicture=!0;this.$video.addEventListener("play",this._onPlay);this.$video.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrEnginePromise=e.createQrEngine()}hasFlash(){if(!("ImageCapture"in window))return Promise.resolve(!1);let a=this.$video.srcObject?this.$video.srcObject.getVideoTracks()[0]:null;return a?(new ImageCapture(a)).getPhotoCapabilities().then(a=>
|
||||
a.fillLightMode.includes("flash")).catch(a=>{console.warn(a);return!1}):Promise.reject("Camera not started or not available")}isFlashOn(){return this._flashOn}toggleFlash(){return this._setFlash(!this._flashOn)}turnFlashOff(){return this._setFlash(!1)}turnFlashOn(){return this._setFlash(!0)}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")}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;this._paused=!1;if(document.hidden)return Promise.resolve();clearTimeout(this._offTimeout);this._offTimeout=null;if(this.$video.srcObject)return this.$video.play(),Promise.resolve();let a=this._preferredFacingMode;return this._getCameraStream(a,
|
||||
!0).catch(()=>{a="environment"===a?"user":"environment";return this._getCameraStream()}).then(b=>{a=this._getFacingMode(b)||a;this.$video.srcObject=b;this.$video.play();this._setVideoMirror(a)}).catch(a=>{this._active=!1;throw a;})}stop(){this.pause();this._active=!1}pause(){this._paused=!0;this._active&&(this.$video.pause(),this._offTimeout||(this._offTimeout=setTimeout(()=>{let a=this.$video.srcObject?this.$video.srcObject.getTracks():[];for(let b of a)b.stop();this._offTimeout=this.$video.srcObject=
|
||||
null},300)))}static scanImage(a,b=null,c=null,d=null,f=!1,k=!1){let h=c instanceof Worker,g=Promise.all([c||e.createQrEngine(),e._loadImage(a)]).then(([a,g])=>{c=a;let k;[d,k]=this._drawToCanvas(g,b,d,f);return c instanceof Worker?(h||c.postMessage({type:"inversionMode",data:"both"}),new Promise((a,b)=>{let f,l,g;l=d=>{"qrResult"===d.data.type&&(c.removeEventListener("message",l),c.removeEventListener("error",g),clearTimeout(f),null!==d.data.data?a(d.data.data):b(e.NO_QR_CODE_FOUND))};g=a=>{c.removeEventListener("message",
|
||||
l);c.removeEventListener("error",g);clearTimeout(f);b("Scanner error: "+(a?a.message||a:"Unknown Error"))};c.addEventListener("message",l);c.addEventListener("error",g);f=setTimeout(()=>g("timeout"),1E4);let h=k.getImageData(0,0,d.width,d.height);c.postMessage({type:"decode",data:h},[h.data.buffer])})):new Promise((a,b)=>{let f=setTimeout(()=>b("Scanner error: timeout"),1E4);c.detect(d).then(c=>{c.length?a(c[0].rawValue):b(e.NO_QR_CODE_FOUND)}).catch(a=>b("Scanner error: "+(a.message||a))).finally(()=>
|
||||
clearTimeout(f))})});b&&k&&(g=g.catch(()=>e.scanImage(a,null,c,d,f)));return g=g.finally(()=>{h||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 createQrEngine(a=e.WORKER_PATH){return("BarcodeDetector"in window?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:(a.videoWidth-b)/2,y:(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=>e.scanImage(this.$video,this._scanRegion,a,this.$canvas)).then(this._onDecode,a=>{this._active&&(-1!==(a.message||a).indexOf("service unavailable")&&(this._qrEnginePromise=e.createQrEngine()),this._onDecodeError(a))}).then(()=>this._scanFrame())})}_onDecodeError(a){a!==e.NO_QR_CODE_FOUND&&
|
||||
console.log(a)}_getCameraStream(a,b=!1){let c=[{width:{min:1024}},{width:{min:768}},{}];a&&(b&&(a={exact:a}),c.forEach(b=>b.facingMode=a));return this._getMatchingCameraStream(c)}_getMatchingCameraStream(a){return navigator.mediaDevices&&0!==a.length?navigator.mediaDevices.getUserMedia({video:a.shift()}).catch(()=>this._getMatchingCameraStream(a)):Promise.reject("Camera not found.")}_setFlash(a){return this.hasFlash().then(b=>b?this.$video.srcObject.getVideoTracks()[0].applyConstraints({advanced:[{torch:a}]}):
|
||||
Promise.reject("No flash available")).then(()=>this._flashOn=a)}_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 f=b&&b.x?b.x:0,k=b&&b.y?b.y:0,h=b&&b.width?b.width:a.width||a.videoWidth,g=b&&b.height?b.height:a.height||a.videoHeight;
|
||||
d||(c.width=b&&b.downScaledWidth?b.downScaledWidth:h,c.height=b&&b.downScaledHeight?b.downScaledHeight:g);b=c.getContext("2d",{alpha:!1});b.imageSmoothingEnabled=!1;b.drawImage(a,f,k,h,g,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 e._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 e._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,f;d=()=>{a.removeEventListener("load",d);a.removeEventListener("error",
|
||||
f);b()};f=()=>{a.removeEventListener("load",d);a.removeEventListener("error",f);c("Image load error")};a.addEventListener("load",d);a.addEventListener("error",f)}})}static _postWorkerMessage(a,b,c){return Promise.resolve(a).then(a=>{a instanceof Worker&&a.postMessage({type:b,data:c})})}}e.DEFAULT_CANVAS_SIZE=400;e.NO_QR_CODE_FOUND="No QR code found";e.WORKER_PATH="qr-scanner-worker.min.js";export default e
|
||||
class e{static hasCamera(){return navigator.mediaDevices?navigator.mediaDevices.enumerateDevices().then(a=>a.some(a=>"videoinput"===a.kind)).catch(()=>!1):Promise.resolve(!1)}static getCameraList(){return navigator.mediaDevices?new Promise((a,b)=>{navigator.mediaDevices.getUserMedia({audio:!1,video:!0}).then(c=>{let d=a=>{const b=a.getVideoTracks();for(let c=0;c<b.length;c+=1){const d=b[c];d.enabled=!1;d.stop();a.removeTrack(d)}};navigator.mediaDevices.enumerateDevices().then(b=>{d(c);
|
||||
a(b.filter(a=>"videoinput"===a.kind).map(a=>({id:a.deviceId,label:a.label})))}).catch(a=>b(a))}).catch(a=>b(a))}):Promise.resolve([])}constructor(a,b,c=this._onDecodeError,d=this._calculateScanRegion,f="environment",h=null){this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;this._legacyCanvasSize=e.DEFAULT_CANVAS_SIZE;this._preferredFacingMode=f;this._deviceId=h;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);this.$video.playsInline=!0;this.$video.muted=!0;this.$video.disablePictureInPicture=!0;this.$video.addEventListener("play",
|
||||
this._onPlay);this.$video.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrEnginePromise=e.createQrEngine()}hasFlash(){if(!("ImageCapture"in window))return Promise.resolve(!1);let a=this.$video.srcObject?this.$video.srcObject.getVideoTracks()[0]:null;return a?(new ImageCapture(a)).getPhotoCapabilities().then(a=>a.fillLightMode.includes("flash")).catch(a=>{console.warn(a);return!1}):Promise.reject("Camera not started or not available")}isFlashOn(){return this._flashOn}toggleFlash(){return this._setFlash(!this._flashOn)}turnFlashOff(){return this._setFlash(!1)}turnFlashOn(){return this._setFlash(!0)}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")}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;this._paused=!1;if(document.hidden)return Promise.resolve();clearTimeout(this._offTimeout);
|
||||
this._offTimeout=null;if(this.$video.srcObject)return this.$video.play(),Promise.resolve();let a=this._preferredFacingMode;return this._getCameraStream(a,this._deviceId,!0).catch(()=>{a="environment"===a?"user":"environment";return this._getCameraStream()}).then(b=>{a=this._getFacingMode(b)||a;this.$video.srcObject=b;this.$video.play();this._setVideoMirror(a)}).catch(a=>{this._active=!1;throw a;})}stop(){this.pause();this._active=!1}pause(){this._paused=!0;this._active&&(this.$video.pause(),this._offTimeout||
|
||||
(this._offTimeout=setTimeout(()=>{let a=this.$video.srcObject?this.$video.srcObject.getTracks():[];for(let b of a)b.stop();this._offTimeout=this.$video.srcObject=null},300)))}setCamera(a){let b=this._active;b&&this.stop();this._deviceId=a;b&&this.start()}static scanImage(a,b=null,c=null,d=null,f=!1,h=!1){let k=c instanceof Worker,g=Promise.all([c||e.createQrEngine(),e._loadImage(a)]).then(([a,g])=>{c=a;let h;[d,h]=this._drawToCanvas(g,b,d,f);return c instanceof Worker?(k||c.postMessage({type:"inversionMode",
|
||||
data:"both"}),new Promise((a,b)=>{let f,l,g;l=d=>{"qrResult"===d.data.type&&(c.removeEventListener("message",l),c.removeEventListener("error",g),clearTimeout(f),null!==d.data.data?a(d.data.data):b(e.NO_QR_CODE_FOUND))};g=a=>{c.removeEventListener("message",l);c.removeEventListener("error",g);clearTimeout(f);b("Scanner error: "+(a?a.message||a:"Unknown Error"))};c.addEventListener("message",l);c.addEventListener("error",g);f=setTimeout(()=>g("timeout"),1E4);let k=h.getImageData(0,0,d.width,d.height);
|
||||
c.postMessage({type:"decode",data:k},[k.data.buffer])})):new Promise((a,b)=>{let f=setTimeout(()=>b("Scanner error: timeout"),1E4);c.detect(d).then(c=>{c.length?a(c[0].rawValue):b(e.NO_QR_CODE_FOUND)}).catch(a=>b("Scanner error: "+(a.message||a))).finally(()=>clearTimeout(f))})});b&&h&&(g=g.catch(()=>e.scanImage(a,null,c,d,f)));return g=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 createQrEngine(a=e.WORKER_PATH){return("BarcodeDetector"in window?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:(a.videoWidth-b)/2,y:(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=>e.scanImage(this.$video,this._scanRegion,a,this.$canvas)).then(this._onDecode,
|
||||
a=>{this._active&&(-1!==(a.message||a).indexOf("service unavailable")&&(this._qrEnginePromise=e.createQrEngine()),this._onDecodeError(a))}).then(()=>this._scanFrame())})}_onDecodeError(a){a!==e.NO_QR_CODE_FOUND&&console.log(a)}_getCameraStream(a,b,c=!1){let d=[{width:{min:1024}},{width:{min:768}},{}];a&&(c&&(a={exact:a}),d.forEach(b=>b.facingMode=a));b&&(c&&(b={exact:b}),d.forEach(a=>a.deviceId=b));return this._getMatchingCameraStream(d)}_getMatchingCameraStream(a){return navigator.mediaDevices&&
|
||||
0!==a.length?navigator.mediaDevices.getUserMedia({video:a.shift()}).catch(()=>this._getMatchingCameraStream(a)):Promise.reject("Camera not found.")}_setFlash(a){return this.hasFlash().then(b=>b?this.$video.srcObject.getVideoTracks()[0].applyConstraints({advanced:[{torch:a}]}):Promise.reject("No flash available")).then(()=>this._flashOn=a)}_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 f=b&&b.x?b.x:0,h=b&&b.y?b.y:0,k=b&&b.width?b.width:a.width||a.videoWidth,g=b&&b.height?b.height:a.height||a.videoHeight;d||(c.width=b&&b.downScaledWidth?b.downScaledWidth:k,c.height=b&&b.downScaledHeight?b.downScaledHeight:g);b=c.getContext("2d",{alpha:!1});b.imageSmoothingEnabled=!1;b.drawImage(a,f,h,k,g,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 e._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 e._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,f;d=()=>{a.removeEventListener("load",d);a.removeEventListener("error",f);b()};f=()=>{a.removeEventListener("load",d);a.removeEventListener("error",f);c("Image load error")};a.addEventListener("load",d);a.addEventListener("error",f)}})}static _postWorkerMessage(a,b,c){return Promise.resolve(a).then(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 @@
|
||||
{"version":3,"file":"qr-scanner.min.js","sources":["src/qr-scanner.js"],"sourcesContent":["export default class QrScanner {\n /* async */\n static hasCamera() {\n if (!navigator.mediaDevices) return Promise.resolve(false);\n // note that enumerateDevices can always be called and does not prompt the user for permission. However, device\n // labels are only readable if served via https and an active media stream exists or permanent permission is\n // given. That doesn't matter for us though as we don't require labels.\n return navigator.mediaDevices.enumerateDevices()\n .then(devices => devices.some(device => device.kind === 'videoinput'))\n .catch(() => false);\n }\n\n constructor(\n video,\n onDecode,\n canvasSizeOrOnDecodeError = this._onDecodeError,\n canvasSizeOrCalculateScanRegion = this._calculateScanRegion,\n preferredFacingMode = 'environment'\n ) {\n this.$video = video;\n this.$canvas = document.createElement('canvas');\n this._onDecode = onDecode;\n this._legacyCanvasSize = QrScanner.DEFAULT_CANVAS_SIZE;\n this._preferredFacingMode = preferredFacingMode;\n this._active = false;\n this._paused = false;\n this._flashOn = false;\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 this._calculateScanRegion = canvasSizeOrCalculateScanRegion;\n }\n\n this._scanRegion = this._calculateScanRegion(video);\n\n this._onPlay = this._onPlay.bind(this);\n this._onLoadedMetaData = this._onLoadedMetaData.bind(this);\n this._onVisibilityChange = this._onVisibilityChange.bind(this);\n\n // Allow inline playback on iPhone instead of requiring full screen playback,\n // see https://webkit.org/blog/6784/new-video-policies-for-ios/\n this.$video.playsInline = true;\n // Allow play() on iPhone without requiring a user gesture. Should not really be needed as camera stream\n // includes no audio, but just to be safe.\n this.$video.muted = true;\n this.$video.disablePictureInPicture = true;\n this.$video.addEventListener('play', this._onPlay);\n this.$video.addEventListener('loadedmetadata', this._onLoadedMetaData);\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n\n this._qrEnginePromise = QrScanner.createQrEngine();\n }\n\n /* async */\n hasFlash() {\n if (!('ImageCapture' in window)) {\n return Promise.resolve(false);\n }\n\n const track = this.$video.srcObject ? this.$video.srcObject.getVideoTracks()[0] : null;\n if (!track) {\n return Promise.reject('Camera not started or not available');\n }\n\n const imageCapture = new ImageCapture(track);\n return imageCapture.getPhotoCapabilities()\n .then((result) => {\n return result.fillLightMode.includes('flash');\n })\n .catch((error) => {\n console.warn(error);\n return false;\n });\n }\n\n isFlashOn() {\n return this._flashOn;\n }\n\n /* async */\n toggleFlash() {\n return this._setFlash(!this._flashOn);\n }\n\n /* async */\n turnFlashOff() {\n return this._setFlash(false);\n }\n\n /* async */\n turnFlashOn() {\n return this._setFlash(true);\n }\n\n destroy() {\n this.$video.removeEventListener('loadedmetadata', this._onLoadedMetaData);\n this.$video.removeEventListener('play', this._onPlay);\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n\n this.stop();\n QrScanner._postWorkerMessage(this._qrEnginePromise, 'close');\n }\n\n /* async */\n start() {\n if (this._active && !this._paused) {\n return Promise.resolve();\n }\n if (window.location.protocol !== 'https:') {\n // warn but try starting the camera anyways\n console.warn('The camera stream is only accessible if the page is transferred via https.');\n }\n tLine truncated
|
||||
{"version":3,"file":"qr-scanner.min.js","sources":["src/qr-scanner.js"],"sourcesContent":["export default class QrScanner {\n /* async */\n static hasCamera() {\n if (!navigator.mediaDevices) return Promise.resolve(false);\n // note that enumerateDevices can always be called and does not prompt the user for permission. However, device\n // labels are only readable if served via https and an active media stream exists or permanent permission is\n // given. That doesn't matter for us though as we don't require labels.\n return navigator.mediaDevices.enumerateDevices()\n .then(devices => devices.some(device => device.kind === 'videoinput'))\n .catch(() => false);\n }\n\n /* async */\n static getCameraList() {\n if (!navigator.mediaDevices) return Promise.resolve([]);\n\n // note that to enumarate devices and get device labels we need explicit user permission so we ask for that.\n return new Promise((resolve, reject) => {\n navigator.mediaDevices.getUserMedia({ audio: false, video: true })\n .then((stream) => {\n // hacky approach to close any active stream if they are\n // active.\n const closeActiveStreams = (stream) => {\n const tracks = stream.getVideoTracks();\n for (let i = 0; i < tracks.length; i += 1) {\n const track = tracks[i];\n track.enabled = false;\n track.stop();\n stream.removeTrack(track);\n }\n };\n\n navigator.mediaDevices.enumerateDevices()\n .then(devices => {\n closeActiveStreams(stream);\n resolve(devices.filter(device => device.kind === 'videoinput').map(device => ({\n id: device.deviceId,\n label: device.label\n })));\n })\n .catch((err) => reject(err));\n }).catch((err) => reject(err));\n });\n }\n\n constructor(\n video,\n onDecode,\n canvasSizeOrOnDecodeError = this._onDecodeError,\n canvasSizeOrCalculateScanRegion = this._calculateScanRegion,\n preferredFacingMode = 'environment',\n deviceId = null\n ) {\n this.$video = video;\n this.$canvas = document.createElement('canvas');\n this._onDecode = onDecode;\n this._legacyCanvasSize = QrScanner.DEFAULT_CANVAS_SIZE;\n this._preferredFacingMode = preferredFacingMode;\n this._deviceId = deviceId;\n this._active = false;\n this._paused = false;\n this._flashOn = false;\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 this._calculateScanRegion = canvasSizeOrCalculateScanRegion;\n }\n\n this._scanRegion = this._calculateScanRegion(video);\n\n this._onPlay = this._onPlay.bind(this);\n this._onLoadedMetaData = this._onLoadedMetaData.bind(this);\n this._onVisibilityChange = this._onVisibilityChange.bind(this);\n\n // Allow inline playback on iPhone instead of requiring full screen playback,\n // see https://webkit.org/blog/6784/new-video-policies-for-ios/\n this.$video.playsInline = true;\n // Allow play() on iPhone without requiring a user gesture. Should not really be needed as camera stream\n // includes no audio, but just to be safe.\n this.$video.muted = true;\n this.$video.disablePictureInPicture = true;\n this.$video.addEventListener('play', this._onPlay);\n this.$video.addEventListener('loadedmetadata', this._onLoadedMetaData);\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n\n this._qrEnginePromise = QrScanner.createQrEngine();\n }\n\n /* async */\n hasFlash() {\n if (!('ImageCapture' in window)) {\n return Promise.resolve(false);\n }\n\n const track = this.Line truncated
|
||||
Vendored
+18
-16
@@ -1,17 +1,19 @@
|
||||
'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 navigator.mediaDevices?navigator.mediaDevices.enumerateDevices().then(a=>a.some(a=>"videoinput"===a.kind)).catch(()=>!1):Promise.resolve(!1)}constructor(a,b,c=this._onDecodeError,e=this._calculateScanRegion,f="environment"){this.$video=a;this.$canvas=document.createElement("canvas");
|
||||
this._onDecode=b;this._legacyCanvasSize=d.DEFAULT_CANVAS_SIZE;this._preferredFacingMode=f;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);this.$video.playsInline=!0;this.$video.muted=!0;this.$video.disablePictureInPicture=!0;this.$video.addEventListener("play",this._onPlay);this.$video.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrEnginePromise=
|
||||
d.createQrEngine()}hasFlash(){if(!("ImageCapture"in window))return Promise.resolve(!1);let a=this.$video.srcObject?this.$video.srcObject.getVideoTracks()[0]:null;return a?(new ImageCapture(a)).getPhotoCapabilities().then(a=>a.fillLightMode.includes("flash")).catch(a=>{console.warn(a);return!1}):Promise.reject("Camera not started or not available")}isFlashOn(){return this._flashOn}toggleFlash(){return this._setFlash(!this._flashOn)}turnFlashOff(){return this._setFlash(!1)}turnFlashOn(){return this._setFlash(!0)}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;this._paused=!1;if(document.hidden)return Promise.resolve();clearTimeout(this._offTimeout);
|
||||
this._offTimeout=null;if(this.$video.srcObject)return this.$video.play(),Promise.resolve();let a=this._preferredFacingMode;return this._getCameraStream(a,!0).catch(()=>{a="environment"===a?"user":"environment";return this._getCameraStream()}).then(b=>{a=this._getFacingMode(b)||a;this.$video.srcObject=b;this.$video.play();this._setVideoMirror(a)}).catch(a=>{this._active=!1;throw a;})}stop(){this.pause();this._active=!1}pause(){this._paused=!0;this._active&&(this.$video.pause(),this._offTimeout||(this._offTimeout=
|
||||
setTimeout(()=>{let a=this.$video.srcObject?this.$video.srcObject.getTracks():[];for(let b of a)b.stop();this._offTimeout=this.$video.srcObject=null},300)))}static scanImage(a,b=null,c=null,e=null,f=!1,k=!1){let h=c instanceof Worker,g=Promise.all([c||d.createQrEngine(),d._loadImage(a)]).then(([a,g])=>{c=a;let k;[e,k]=this._drawToCanvas(g,b,e,f);return c instanceof Worker?(h||c.postMessage({type:"inversionMode",data:"both"}),new Promise((a,b)=>{let f,l,g;l=e=>{"qrResult"===e.data.type&&(c.removeEventListener("message",
|
||||
l),c.removeEventListener("error",g),clearTimeout(f),null!==e.data.data?a(e.data.data):b(d.NO_QR_CODE_FOUND))};g=a=>{c.removeEventListener("message",l);c.removeEventListener("error",g);clearTimeout(f);b("Scanner error: "+(a?a.message||a:"Unknown Error"))};c.addEventListener("message",l);c.addEventListener("error",g);f=setTimeout(()=>g("timeout"),1E4);let h=k.getImageData(0,0,e.width,e.height);c.postMessage({type:"decode",data:h},[h.data.buffer])})):new Promise((a,b)=>{let f=setTimeout(()=>b("Scanner error: timeout"),
|
||||
1E4);c.detect(e).then(c=>{c.length?a(c[0].rawValue):b(d.NO_QR_CODE_FOUND)}).catch(a=>b("Scanner error: "+(a.message||a))).finally(()=>clearTimeout(f))})});b&&k&&(g=g.catch(()=>d.scanImage(a,null,c,e,f)));return g=g.finally(()=>{h||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 createQrEngine(a=
|
||||
d.WORKER_PATH){return("BarcodeDetector"in window?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:(a.videoWidth-b)/2,y:(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(a,b=!1){let c=[{width:{min:1024}},{width:{min:768}},{}];a&&(b&&(a={exact:a}),c.forEach(b=>b.facingMode=a));return this._getMatchingCameraStream(c)}_getMatchingCameraStream(a){return navigator.mediaDevices&&0!==a.length?navigator.mediaDevices.getUserMedia({video:a.shift()}).catch(()=>this._getMatchingCameraStream(a)):Promise.reject("Camera not found.")}_setFlash(a){return this.hasFlash().then(b=>
|
||||
b?this.$video.srcObject.getVideoTracks()[0].applyConstraints({advanced:[{torch:a}]}):Promise.reject("No flash available")).then(()=>this._flashOn=a)}_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,e=!1){c=c||document.createElement("canvas");let d=b&&b.x?b.x:0,k=b&&b.y?b.y:0,h=b&&
|
||||
b.width?b.width:a.width||a.videoWidth,g=b&&b.height?b.height:a.height||a.videoHeight;e||(c.width=b&&b.downScaledWidth?b.downScaledWidth:h,c.height=b&&b.downScaledHeight?b.downScaledHeight:g);b=c.getContext("2d",{alpha:!1});b.imageSmoothingEnabled=!1;b.drawImage(a,d,k,h,g,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 d,f;d=()=>
|
||||
{a.removeEventListener("load",d);a.removeEventListener("error",f);b()};f=()=>{a.removeEventListener("load",d);a.removeEventListener("error",f);c("Image load error")};a.addEventListener("load",d);a.addEventListener("error",f)}})}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(e,a){"object"===typeof exports&&"undefined"!==typeof module?module.exports=a():"function"===typeof define&&define.amd?define(a):(e=e||self,e.QrScanner=a())})(this,function(){class e{static hasCamera(){return navigator.mediaDevices?navigator.mediaDevices.enumerateDevices().then(a=>a.some(a=>"videoinput"===a.kind)).catch(()=>!1):Promise.resolve(!1)}static getCameraList(){return navigator.mediaDevices?new Promise((a,b)=>{navigator.mediaDevices.getUserMedia({audio:!1,video:!0}).then(c=>
|
||||
{let d=a=>{const b=a.getVideoTracks();for(let c=0;c<b.length;c+=1){const d=b[c];d.enabled=!1;d.stop();a.removeTrack(d)}};navigator.mediaDevices.enumerateDevices().then(b=>{d(c);a(b.filter(a=>"videoinput"===a.kind).map(a=>({id:a.deviceId,label:a.label})))}).catch(a=>b(a))}).catch(a=>b(a))}):Promise.resolve([])}constructor(a,b,c=this._onDecodeError,d=this._calculateScanRegion,g="environment",h=null){this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;this._legacyCanvasSize=
|
||||
e.DEFAULT_CANVAS_SIZE;this._preferredFacingMode=g;this._deviceId=h;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);this.$video.playsInline=!0;this.$video.muted=!0;this.$video.disablePictureInPicture=!0;this.$video.addEventListener("play",this._onPlay);this.$video.addEventListener("loadedmetadata",this._onLoadedMetaData);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrEnginePromise=e.createQrEngine()}hasFlash(){if(!("ImageCapture"in
|
||||
window))return Promise.resolve(!1);let a=this.$video.srcObject?this.$video.srcObject.getVideoTracks()[0]:null;return a?(new ImageCapture(a)).getPhotoCapabilities().then(a=>a.fillLightMode.includes("flash")).catch(a=>{console.warn(a);return!1}):Promise.reject("Camera not started or not available")}isFlashOn(){return this._flashOn}toggleFlash(){return this._setFlash(!this._flashOn)}turnFlashOff(){return this._setFlash(!1)}turnFlashOn(){return this._setFlash(!0)}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")}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;this._paused=!1;if(document.hidden)return Promise.resolve();clearTimeout(this._offTimeout);
|
||||
this._offTimeout=null;if(this.$video.srcObject)return this.$video.play(),Promise.resolve();let a=this._preferredFacingMode;return this._getCameraStream(a,this._deviceId,!0).catch(()=>{a="environment"===a?"user":"environment";return this._getCameraStream()}).then(b=>{a=this._getFacingMode(b)||a;this.$video.srcObject=b;this.$video.play();this._setVideoMirror(a)}).catch(a=>{this._active=!1;throw a;})}stop(){this.pause();this._active=!1}pause(){this._paused=!0;this._active&&(this.$video.pause(),this._offTimeout||
|
||||
(this._offTimeout=setTimeout(()=>{let a=this.$video.srcObject?this.$video.srcObject.getTracks():[];for(let b of a)b.stop();this._offTimeout=this.$video.srcObject=null},300)))}setCamera(a){let b=this._active;b&&this.stop();this._deviceId=a;b&&this.start()}static scanImage(a,b=null,c=null,d=null,g=!1,h=!1){let k=c instanceof Worker,f=Promise.all([c||e.createQrEngine(),e._loadImage(a)]).then(([a,f])=>{c=a;let h;[d,h]=this._drawToCanvas(f,b,d,g);return c instanceof Worker?(k||c.postMessage({type:"inversionMode",
|
||||
data:"both"}),new Promise((a,b)=>{let g,l,f;l=d=>{"qrResult"===d.data.type&&(c.removeEventListener("message",l),c.removeEventListener("error",f),clearTimeout(g),null!==d.data.data?a(d.data.data):b(e.NO_QR_CODE_FOUND))};f=a=>{c.removeEventListener("message",l);c.removeEventListener("error",f);clearTimeout(g);b("Scanner error: "+(a?a.message||a:"Unknown Error"))};c.addEventListener("message",l);c.addEventListener("error",f);g=setTimeout(()=>f("timeout"),1E4);let k=h.getImageData(0,0,d.width,d.height);
|
||||
c.postMessage({type:"decode",data:k},[k.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(e.NO_QR_CODE_FOUND)}).catch(a=>b("Scanner error: "+(a.message||a))).finally(()=>clearTimeout(g))})});b&&h&&(f=f.catch(()=>e.scanImage(a,null,c,d,g)));return f=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 createQrEngine(a=e.WORKER_PATH){return("BarcodeDetector"in window?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:(a.videoWidth-b)/2,y:(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=>e.scanImage(this.$video,this._scanRegion,a,this.$canvas)).then(this._onDecode,
|
||||
a=>{this._active&&(-1!==(a.message||a).indexOf("service unavailable")&&(this._qrEnginePromise=e.createQrEngine()),this._onDecodeError(a))}).then(()=>this._scanFrame())})}_onDecodeError(a){a!==e.NO_QR_CODE_FOUND&&console.log(a)}_getCameraStream(a,b,c=!1){let d=[{width:{min:1024}},{width:{min:768}},{}];a&&(c&&(a={exact:a}),d.forEach(b=>b.facingMode=a));b&&(c&&(b={exact:b}),d.forEach(a=>a.deviceId=b));return this._getMatchingCameraStream(d)}_getMatchingCameraStream(a){return navigator.mediaDevices&&
|
||||
0!==a.length?navigator.mediaDevices.getUserMedia({video:a.shift()}).catch(()=>this._getMatchingCameraStream(a)):Promise.reject("Camera not found.")}_setFlash(a){return this.hasFlash().then(b=>b?this.$video.srcObject.getVideoTracks()[0].applyConstraints({advanced:[{torch:a}]}):Promise.reject("No flash available")).then(()=>this._flashOn=a)}_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,h=b&&b.y?b.y:0,k=b&&b.width?b.width:a.width||a.videoWidth,f=b&&b.height?b.height:a.height||a.videoHeight;d||(c.width=b&&b.downScaledWidth?b.downScaledWidth:k,c.height=b&&b.downScaledHeight?b.downScaledHeight:f);b=c.getContext("2d",{alpha:!1});b.imageSmoothingEnabled=!1;b.drawImage(a,e,h,k,f,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 e._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 e._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,e;d=()=>{a.removeEventListener("load",d);a.removeEventListener("error",e);b()};e=()=>{a.removeEventListener("load",d);a.removeEventListener("error",e);c("Image load error")};a.addEventListener("load",d);a.addEventListener("error",e)}})}static _postWorkerMessage(a,b,c){return Promise.resolve(a).then(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 @@
|
||||
{"version":3,"file":"qr-scanner.umd.min.js","sources":["src/qr-scanner.js"],"sourcesContent":["export default class QrScanner {\n /* async */\n static hasCamera() {\n if (!navigator.mediaDevices) return Promise.resolve(false);\n // note that enumerateDevices can always be called and does not prompt the user for permission. However, device\n // labels are only readable if served via https and an active media stream exists or permanent permission is\n // given. That doesn't matter for us though as we don't require labels.\n return navigator.mediaDevices.enumerateDevices()\n .then(devices => devices.some(device => device.kind === 'videoinput'))\n .catch(() => false);\n }\n\n constructor(\n video,\n onDecode,\n canvasSizeOrOnDecodeError = this._onDecodeError,\n canvasSizeOrCalculateScanRegion = this._calculateScanRegion,\n preferredFacingMode = 'environment'\n ) {\n this.$video = video;\n this.$canvas = document.createElement('canvas');\n this._onDecode = onDecode;\n this._legacyCanvasSize = QrScanner.DEFAULT_CANVAS_SIZE;\n this._preferredFacingMode = preferredFacingMode;\n this._active = false;\n this._paused = false;\n this._flashOn = false;\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 this._calculateScanRegion = canvasSizeOrCalculateScanRegion;\n }\n\n this._scanRegion = this._calculateScanRegion(video);\n\n this._onPlay = this._onPlay.bind(this);\n this._onLoadedMetaData = this._onLoadedMetaData.bind(this);\n this._onVisibilityChange = this._onVisibilityChange.bind(this);\n\n // Allow inline playback on iPhone instead of requiring full screen playback,\n // see https://webkit.org/blog/6784/new-video-policies-for-ios/\n this.$video.playsInline = true;\n // Allow play() on iPhone without requiring a user gesture. Should not really be needed as camera stream\n // includes no audio, but just to be safe.\n this.$video.muted = true;\n this.$video.disablePictureInPicture = true;\n this.$video.addEventListener('play', this._onPlay);\n this.$video.addEventListener('loadedmetadata', this._onLoadedMetaData);\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n\n this._qrEnginePromise = QrScanner.createQrEngine();\n }\n\n /* async */\n hasFlash() {\n if (!('ImageCapture' in window)) {\n return Promise.resolve(false);\n }\n\n const track = this.$video.srcObject ? this.$video.srcObject.getVideoTracks()[0] : null;\n if (!track) {\n return Promise.reject('Camera not started or not available');\n }\n\n const imageCapture = new ImageCapture(track);\n return imageCapture.getPhotoCapabilities()\n .then((result) => {\n return result.fillLightMode.includes('flash');\n })\n .catch((error) => {\n console.warn(error);\n return false;\n });\n }\n\n isFlashOn() {\n return this._flashOn;\n }\n\n /* async */\n toggleFlash() {\n return this._setFlash(!this._flashOn);\n }\n\n /* async */\n turnFlashOff() {\n return this._setFlash(false);\n }\n\n /* async */\n turnFlashOn() {\n return this._setFlash(true);\n }\n\n destroy() {\n this.$video.removeEventListener('loadedmetadata', this._onLoadedMetaData);\n this.$video.removeEventListener('play', this._onPlay);\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n\n this.stop();\n QrScanner._postWorkerMessage(this._qrEnginePromise, 'close');\n }\n\n /* async */\n start() {\n if (this._active && !this._paused) {\n return Promise.resolve();\n }\n if (window.location.protocol !== 'https:') {\n // warn but try starting the camera anyways\n console.warn('The camera stream is only accessible if the page is transferred via https.');\n }\n Line truncated
|
||||
{"version":3,"file":"qr-scanner.umd.min.js","sources":["src/qr-scanner.js"],"sourcesContent":["export default class QrScanner {\n /* async */\n static hasCamera() {\n if (!navigator.mediaDevices) return Promise.resolve(false);\n // note that enumerateDevices can always be called and does not prompt the user for permission. However, device\n // labels are only readable if served via https and an active media stream exists or permanent permission is\n // given. That doesn't matter for us though as we don't require labels.\n return navigator.mediaDevices.enumerateDevices()\n .then(devices => devices.some(device => device.kind === 'videoinput'))\n .catch(() => false);\n }\n\n /* async */\n static getCameraList() {\n if (!navigator.mediaDevices) return Promise.resolve([]);\n\n // note that to enumarate devices and get device labels we need explicit user permission so we ask for that.\n return new Promise((resolve, reject) => {\n navigator.mediaDevices.getUserMedia({ audio: false, video: true })\n .then((stream) => {\n // hacky approach to close any active stream if they are\n // active.\n const closeActiveStreams = (stream) => {\n const tracks = stream.getVideoTracks();\n for (let i = 0; i < tracks.length; i += 1) {\n const track = tracks[i];\n track.enabled = false;\n track.stop();\n stream.removeTrack(track);\n }\n };\n\n navigator.mediaDevices.enumerateDevices()\n .then(devices => {\n closeActiveStreams(stream);\n resolve(devices.filter(device => device.kind === 'videoinput').map(device => ({\n id: device.deviceId,\n label: device.label\n })));\n })\n .catch((err) => reject(err));\n }).catch((err) => reject(err));\n });\n }\n\n constructor(\n video,\n onDecode,\n canvasSizeOrOnDecodeError = this._onDecodeError,\n canvasSizeOrCalculateScanRegion = this._calculateScanRegion,\n preferredFacingMode = 'environment',\n deviceId = null\n ) {\n this.$video = video;\n this.$canvas = document.createElement('canvas');\n this._onDecode = onDecode;\n this._legacyCanvasSize = QrScanner.DEFAULT_CANVAS_SIZE;\n this._preferredFacingMode = preferredFacingMode;\n this._deviceId = deviceId;\n this._active = false;\n this._paused = false;\n this._flashOn = false;\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 this._calculateScanRegion = canvasSizeOrCalculateScanRegion;\n }\n\n this._scanRegion = this._calculateScanRegion(video);\n\n this._onPlay = this._onPlay.bind(this);\n this._onLoadedMetaData = this._onLoadedMetaData.bind(this);\n this._onVisibilityChange = this._onVisibilityChange.bind(this);\n\n // Allow inline playback on iPhone instead of requiring full screen playback,\n // see https://webkit.org/blog/6784/new-video-policies-for-ios/\n this.$video.playsInline = true;\n // Allow play() on iPhone without requiring a user gesture. Should not really be needed as camera stream\n // includes no audio, but just to be safe.\n this.$video.muted = true;\n this.$video.disablePictureInPicture = true;\n this.$video.addEventListener('play', this._onPlay);\n this.$video.addEventListener('loadedmetadata', this._onLoadedMetaData);\n document.addEventListener('visibilitychange', this._onVisibilityChange);\n\n this._qrEnginePromise = QrScanner.createQrEngine();\n }\n\n /* async */\n hasFlash() {\n if (!('ImageCapture' in window)) {\n return Promise.resolve(false);\n }\n\n const track = tLine truncated
|
||||
+56
-3
@@ -10,18 +10,53 @@ export default class QrScanner {
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
/* async */
|
||||
static getCameraList() {
|
||||
if (!navigator.mediaDevices) return Promise.resolve([]);
|
||||
|
||||
// note that to enumarate devices and get device labels we need explicit user permission so we ask for that.
|
||||
return new Promise((resolve, reject) => {
|
||||
navigator.mediaDevices.getUserMedia({ audio: false, video: true })
|
||||
.then((stream) => {
|
||||
// hacky approach to close any active stream if they are
|
||||
// active.
|
||||
const closeActiveStreams = (stream) => {
|
||||
const tracks = stream.getVideoTracks();
|
||||
for (let i = 0; i < tracks.length; i += 1) {
|
||||
const track = tracks[i];
|
||||
track.enabled = false;
|
||||
track.stop();
|
||||
stream.removeTrack(track);
|
||||
}
|
||||
};
|
||||
|
||||
navigator.mediaDevices.enumerateDevices()
|
||||
.then(devices => {
|
||||
closeActiveStreams(stream);
|
||||
resolve(devices.filter(device => device.kind === 'videoinput').map(device => ({
|
||||
id: device.deviceId,
|
||||
label: device.label
|
||||
})));
|
||||
})
|
||||
.catch((err) => reject(err));
|
||||
}).catch((err) => reject(err));
|
||||
});
|
||||
}
|
||||
|
||||
constructor(
|
||||
video,
|
||||
onDecode,
|
||||
canvasSizeOrOnDecodeError = this._onDecodeError,
|
||||
canvasSizeOrCalculateScanRegion = this._calculateScanRegion,
|
||||
preferredFacingMode = 'environment'
|
||||
preferredFacingMode = 'environment',
|
||||
deviceId = null
|
||||
) {
|
||||
this.$video = video;
|
||||
this.$canvas = document.createElement('canvas');
|
||||
this._onDecode = onDecode;
|
||||
this._legacyCanvasSize = QrScanner.DEFAULT_CANVAS_SIZE;
|
||||
this._preferredFacingMode = preferredFacingMode;
|
||||
this._deviceId = deviceId;
|
||||
this._active = false;
|
||||
this._paused = false;
|
||||
this._flashOn = false;
|
||||
@@ -138,7 +173,8 @@ export default class QrScanner {
|
||||
}
|
||||
|
||||
let facingMode = this._preferredFacingMode;
|
||||
return this._getCameraStream(facingMode, true)
|
||||
let deviceId = this._deviceId;
|
||||
return this._getCameraStream(facingMode, deviceId, true)
|
||||
.catch(() => {
|
||||
// We (probably) don't have a camera of the requested facing mode
|
||||
facingMode = facingMode === 'environment' ? 'user' : 'environment';
|
||||
@@ -182,6 +218,17 @@ export default class QrScanner {
|
||||
}, 300);
|
||||
}
|
||||
|
||||
setCamera(deviceId) {
|
||||
const active = this._active;
|
||||
if (active) {
|
||||
this.stop();
|
||||
}
|
||||
this._deviceId = deviceId;
|
||||
if (active) {
|
||||
this.start();
|
||||
}
|
||||
}
|
||||
|
||||
/* async */
|
||||
static scanImage(imageOrFileOrUrl, scanRegion=null, qrEngine=null, canvas=null, fixedCanvasSize=false,
|
||||
alsoTryWithoutScanRegion=false) {
|
||||
@@ -346,7 +393,7 @@ export default class QrScanner {
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
_getCameraStream(facingMode, exact = false) {
|
||||
_getCameraStream(facingMode, deviceId, exact = false) {
|
||||
const constraintsToTry = [{
|
||||
width: { min: 1024 }
|
||||
}, {
|
||||
@@ -359,6 +406,12 @@ export default class QrScanner {
|
||||
}
|
||||
constraintsToTry.forEach(constraint => constraint.facingMode = facingMode);
|
||||
}
|
||||
if (deviceId) {
|
||||
if (exact) {
|
||||
deviceId = { exact: deviceId };
|
||||
}
|
||||
constraintsToTry.forEach(constraint => constraint.deviceId = deviceId);
|
||||
}
|
||||
return this._getMatchingCameraStream(constraintsToTry);
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user