mirror of
https://github.com/nimiq/qr-scanner.git
synced 2026-07-28 04:06:51 +00:00
Improvements and fixes for continous scanning error handling
- Make change backwards compatible with previous consructor signature - Fix demo to interpret errors as proper results - Update types - Do not report errors when the scanner is inactive - Update readme
This commit is contained in:
@@ -105,9 +105,11 @@ Create a `<video>` element where the web cam video stream should get rendered:
|
||||
|
||||
#### 2. Create a QrScanner Instance
|
||||
```js
|
||||
const qrScanner = new QrScanner(videoElem, result => console.log('decoded qr code:', result), error => console.log(error || 'No QR code found.'));
|
||||
const qrScanner = new QrScanner(videoElem, result => console.log('decoded qr code:', result));
|
||||
```
|
||||
As an optional third parameter a specific resolution that should be worked on can be specified. The default is 400.
|
||||
As an optional third parameter an error handler to be invoked on decoding errors can be specified. The default is `QrScanner._onDecodeError`.
|
||||
|
||||
As an optional fourth parameter a specific resolution that should be worked on can be specified. The default is 400.
|
||||
|
||||
#### 3. Start scanning
|
||||
```js
|
||||
|
||||
+4
-1
@@ -73,7 +73,10 @@
|
||||
|
||||
QrScanner.hasCamera().then(hasCamera => camHasCamera.textContent = hasCamera);
|
||||
|
||||
const scanner = new QrScanner(video, result => setResult(camQrResult, result), error => setResult(camQrResult, error || 'No QR code found.'));
|
||||
const scanner = new QrScanner(video, result => setResult(camQrResult, result), error => {
|
||||
camQrResult.textContent = error;
|
||||
camQrResult.style.color = 'inherit';
|
||||
});
|
||||
scanner.start();
|
||||
|
||||
document.getElementById('inversion-mode-select').addEventListener('change', event => {
|
||||
|
||||
Vendored
+12
-11
@@ -1,12 +1,13 @@
|
||||
class e{static hasCamera(){return!!navigator.mediaDevices&&navigator.mediaDevices.enumerateDevices().then((a)=>a.some((a)=>"videoinput"===a.kind)).catch(()=>!1)}constructor(a,b,c,d=e.DEFAULT_CANVAS_SIZE){this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;this._onDecodeError=c;this._paused=this._active=!1;this.$canvas.width=d;this.$canvas.height=d;this._sourceRect={x:0,y:0,width:d,height:d};this._onPlay=this._onPlay.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);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrWorker=new Worker(e.WORKER_PATH)}destroy(){this.$video.removeEventListener("play",this._onPlay);document.removeEventListener("visibilitychange",this._onVisibilityChange);this.stop();this._qrWorker.postMessage({type:"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="environment";return this._getCameraStream("environment",!0).catch(()=>{a="user";return this._getCameraStream()}).then((b)=>{this._isUserFacing(b)&&(a="user");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,g=!1){let h=!1,l=new Promise((l,g)=>{c||(c=new Worker(e.WORKER_PATH),h=!0,c.postMessage({type:"inversionMode",
|
||||
data:"both"}));let n,m,k;m=(a)=>{"qrResult"===a.data.type&&(c.removeEventListener("message",m),c.removeEventListener("error",k),clearTimeout(n),null!==a.data.data?l(a.data.data):g("QR code not found."))};k=(a)=>{c.removeEventListener("message",m);c.removeEventListener("error",k);clearTimeout(n);g("Scanner error: "+(a?a.message||a:"Unknown Error"))};c.addEventListener("message",m);c.addEventListener("error",k);n=setTimeout(()=>k("timeout"),3E3);e._loadImage(a).then((a)=>{a=e._getImageData(a,b,d,f);
|
||||
c.postMessage({type:"decode",data:a},[a.data.buffer])}).catch(k)});b&&g&&(l=l.catch(()=>e.scanImage(a,null,c,d,f)));return l=l.finally(()=>{h&&c.postMessage({type:"close"})})}setGrayscaleWeights(a,b,c,d=!0){this._qrWorker.postMessage({type:"grayscaleWeights",data:{red:a,green:b,blue:c,useIntegerApproximation:d}})}setInversionMode(a){this._qrWorker.postMessage({type:"inversionMode",data:a})}_onPlay(){this._updateSourceRect();this._scanFrame()}_onVisibilityChange(){document.hidden?this.pause():this._active&&
|
||||
this.start()}_updateSourceRect(){let a=Math.round(2/3*Math.min(this.$video.videoWidth,this.$video.videoHeight));this._sourceRect.width=this._sourceRect.height=a;this._sourceRect.x=(this.$video.videoWidth-a)/2;this._sourceRect.y=(this.$video.videoHeight-a)/2}_scanFrame(){if(!this._active||this.$video.paused||this.$video.ended)return!1;requestAnimationFrame(()=>{1>=this.$video.readyState?this._scanFrame():e.scanImage(this.$video,this._sourceRect,this._qrWorker,this.$canvas,!0).then(this._onDecode,this._onDecodeError).then(()=>
|
||||
this._scanFrame())})}_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.")}_setVideoMirror(a){this.$video.style.transform="scaleX("+("user"===a?-1:1)+")"}_isUserFacing(a){return(a=a.getVideoTracks()[0])?
|
||||
/front|user|face/i.test(a.label):!1}static _getImageData(a,b=null,c=null,d=!1){c=c||document.createElement("canvas");let f=b&&b.x?b.x:0,g=b&&b.y?b.y:0,h=b&&b.width?b.width:a.width||a.videoWidth;b=b&&b.height?b.height:a.height||a.videoHeight;d||c.width===h&&c.height===b||(c.width=h,c.height=b);d=c.getContext("2d",{alpha:!1});d.imageSmoothingEnabled=!1;d.drawImage(a,f,g,h,b,0,0,c.width,c.height);return d.getImageData(0,0,c.width,c.height)}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 URL||"string"===typeof a){let b=new Image;b.src=a instanceof File?URL.createObjectURL(a):a;return e._awaitImageLoad(b).then(()=>{a instanceof File&&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)}})}}e.DEFAULT_CANVAS_SIZE=400;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)}constructor(a,b,c=this._onDecodeError.bind(this),d=e.DEFAULT_CANVAS_SIZE){this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;this._paused=this._active=!1;"number"===typeof c?(d=c,console.warn("You're using a deprecated version of the QrScanner constructor which will be removed in the future")):this._onDecodeError=
|
||||
c;this.$canvas.width=d;this.$canvas.height=d;this._sourceRect={x:0,y:0,width:d,height:d};this._onPlay=this._onPlay.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);document.addEventListener("visibilitychange",this._onVisibilityChange);this._qrWorker=new Worker(e.WORKER_PATH)}destroy(){this.$video.removeEventListener("play",this._onPlay);document.removeEventListener("visibilitychange",
|
||||
this._onVisibilityChange);this.stop();this._qrWorker.postMessage({type:"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="environment";return this._getCameraStream("environment",
|
||||
!0).catch(()=>{a="user";return this._getCameraStream()}).then((b)=>{this._isUserFacing(b)&&(a="user");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,g=!1){let h=!1,l=new Promise((l,g)=>{c||(c=new Worker(e.WORKER_PATH),h=!0,c.postMessage({type:"inversionMode",data:"both"}));let n,m,k;m=(a)=>{"qrResult"===a.data.type&&(c.removeEventListener("message",m),c.removeEventListener("error",k),clearTimeout(n),null!==a.data.data?l(a.data.data):g(e.NO_QR_CODE_FOUND))};k=(a)=>{c.removeEventListener("message",m);c.removeEventListener("error",k);clearTimeout(n);g("Scanner error: "+(a?a.message||a:"Unknown Error"))};c.addEventListener("message",
|
||||
m);c.addEventListener("error",k);n=setTimeout(()=>k("timeout"),3E3);e._loadImage(a).then((a)=>{a=e._getImageData(a,b,d,f);c.postMessage({type:"decode",data:a},[a.data.buffer])}).catch(k)});b&&g&&(l=l.catch(()=>e.scanImage(a,null,c,d,f)));return l=l.finally(()=>{h&&c.postMessage({type:"close"})})}setGrayscaleWeights(a,b,c,d=!0){this._qrWorker.postMessage({type:"grayscaleWeights",data:{red:a,green:b,blue:c,useIntegerApproximation:d}})}setInversionMode(a){this._qrWorker.postMessage({type:"inversionMode",
|
||||
data:a})}_onPlay(){this._updateSourceRect();this._scanFrame()}_onVisibilityChange(){document.hidden?this.pause():this._active&&this.start()}_updateSourceRect(){let a=Math.round(2/3*Math.min(this.$video.videoWidth,this.$video.videoHeight));this._sourceRect.width=this._sourceRect.height=a;this._sourceRect.x=(this.$video.videoWidth-a)/2;this._sourceRect.y=(this.$video.videoHeight-a)/2}_scanFrame(){if(!this._active||this.$video.paused||this.$video.ended)return!1;requestAnimationFrame(()=>{1>=this.$video.readyState?
|
||||
this._scanFrame():e.scanImage(this.$video,this._sourceRect,this._qrWorker,this.$canvas,!0).then(this._onDecode,(a)=>{this._active&&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.")}_setVideoMirror(a){this.$video.style.transform="scaleX("+("user"===a?-1:1)+")"}_isUserFacing(a){return(a=a.getVideoTracks()[0])?/front|user|face/i.test(a.label):!1}static _getImageData(a,b=null,c=null,d=!1){c=c||document.createElement("canvas");let f=b&&b.x?b.x:0,g=b&&b.y?b.y:0,h=b&&b.width?b.width:a.width||a.videoWidth;b=b&&b.height?b.height:a.height||a.videoHeight;d||c.width===h&&c.height===b||(c.width=h,c.height=b);d=c.getContext("2d",
|
||||
{alpha:!1});d.imageSmoothingEnabled=!1;d.drawImage(a,f,g,h,b,0,0,c.width,c.height);return d.getImageData(0,0,c.width,c.height)}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 URL||"string"===typeof a){let b=new Image;b.src=a instanceof
|
||||
File?URL.createObjectURL(a):a;return e._awaitImageLoad(b).then(()=>{a instanceof File&&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)}})}}e.DEFAULT_CANVAS_SIZE=400;e.NO_QR_CODE_FOUND="No QR code found";e.WORKER_PATH="qr-scanner-worker.min.js";export default e;
|
||||
//# sourceMappingURL=qr-scanner.min.js.map
|
||||
|
||||
File diff suppressed because one or more lines are too long
+27
-4
@@ -9,14 +9,27 @@ export default class QrScanner {
|
||||
.catch(() => false);
|
||||
}
|
||||
|
||||
constructor(video, onDecode, onDecodeError, canvasSize = QrScanner.DEFAULT_CANVAS_SIZE) {
|
||||
constructor(
|
||||
video,
|
||||
onDecode,
|
||||
canvasSizeOrOnDecodeError = this._onDecodeError.bind(this),
|
||||
canvasSize = QrScanner.DEFAULT_CANVAS_SIZE
|
||||
) {
|
||||
this.$video = video;
|
||||
this.$canvas = document.createElement('canvas');
|
||||
this._onDecode = onDecode;
|
||||
this._onDecodeError = onDecodeError;
|
||||
this._active = false;
|
||||
this._paused = false;
|
||||
|
||||
if (typeof canvasSizeOrOnDecodeError === 'number') {
|
||||
// legacy function signature where canvas size is the third argument
|
||||
canvasSize = canvasSizeOrOnDecodeError;
|
||||
console.warn('You\'re using a deprecated version of the QrScanner constructor which will be removed in '
|
||||
+ 'the future');
|
||||
} else {
|
||||
this._onDecodeError = canvasSizeOrOnDecodeError;
|
||||
}
|
||||
|
||||
this.$canvas.width = canvasSize;
|
||||
this.$canvas.height = canvasSize;
|
||||
this._sourceRect = {
|
||||
@@ -143,7 +156,7 @@ export default class QrScanner {
|
||||
if (event.data.data !== null) {
|
||||
resolve(event.data.data);
|
||||
} else {
|
||||
reject('QR code not found.');
|
||||
reject(QrScanner.NO_QR_CODE_FOUND);
|
||||
}
|
||||
};
|
||||
onError = (e) => {
|
||||
@@ -227,11 +240,20 @@ export default class QrScanner {
|
||||
return;
|
||||
}
|
||||
QrScanner.scanImage(this.$video, this._sourceRect, this._qrWorker, this.$canvas, true)
|
||||
.then(this._onDecode, this._onDecodeError)
|
||||
.then(this._onDecode, (error) => {
|
||||
if (!this._active) return;
|
||||
this._onDecodeError(error);
|
||||
})
|
||||
.then(() => this._scanFrame());
|
||||
});
|
||||
}
|
||||
|
||||
_onDecodeError(error) {
|
||||
// default error handler; can be overwritten in the constructor
|
||||
if (error === QrScanner.NO_QR_CODE_FOUND) return;
|
||||
console.log(error);
|
||||
}
|
||||
|
||||
_getCameraStream(facingMode, exact = false) {
|
||||
const constraintsToTry = [{
|
||||
width: { min: 1024 }
|
||||
@@ -338,4 +360,5 @@ export default class QrScanner {
|
||||
}
|
||||
}
|
||||
QrScanner.DEFAULT_CANVAS_SIZE = 400;
|
||||
QrScanner.NO_QR_CODE_FOUND = 'No QR code found';
|
||||
QrScanner.WORKER_PATH = 'qr-scanner-worker.min.js';
|
||||
|
||||
Vendored
+8
@@ -4,10 +4,18 @@
|
||||
|
||||
declare class QrScanner {
|
||||
static DEFAULT_CANVAS_SIZE: number;
|
||||
static NO_QR_CODE_FOUND: string;
|
||||
static WORKER_PATH: string;
|
||||
|
||||
static hasCamera(): Promise<boolean>;
|
||||
|
||||
constructor(
|
||||
video: HTMLVideoElement,
|
||||
onDecode: (result: string) => void,
|
||||
onDecodeError?: (error: string) => void,
|
||||
canvasSize?: number,
|
||||
);
|
||||
/** @deprecated */
|
||||
constructor(video: HTMLVideoElement, onDecode: (result: string) => void, canvasSize?: number);
|
||||
|
||||
destroy(): void;
|
||||
|
||||
Reference in New Issue
Block a user