mirror of
https://github.com/nimiq/qr-scanner.git
synced 2026-09-22 18:23:54 +00:00
Allow specifying a preferred facingMode for camera
PR #35 Co-authored-by: Daniel <github.danimoh@neverbox.com>
This commit is contained in:
1 parent
947fb7ebcf
commit
49ccc1dde9
5 files changed
+35
-26
No files matched your search
@@ -111,6 +111,10 @@ As an optional third parameter an error handler to be invoked on decoding errors
|
||||
|
||||
As an optional fourth parameter a specific resolution that should be worked on can be specified. The default is 400.
|
||||
|
||||
As an optional fifth parameter a preference for the camera to use can be specified. Allowed values are `'environment'` and `'user'`. The default is `'environment'`.
|
||||
|
||||
To use the default value for an optional parameter, omit it or use `undefined`.
|
||||
|
||||
#### 3. Start scanning
|
||||
```js
|
||||
qrScanner.start();
|
||||
|
||||
Vendored
+12
-12
@@ -1,13 +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=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;
|
||||
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,f="environment"){this.$video=a;this.$canvas=document.createElement("canvas");this._onDecode=b;this._preferredFacingMode=f;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=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,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)+")"}_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 _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
|
||||
@@ -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 // 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 && 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.bind(this),\n canvasSize = QrScanner.DEFAULT_CANVAS_SIZE\n ) {\n this.$video = video;\n this.$canvas = document.createElement('canvas');\n this._onDecode = onDecode;\n this._active = false;\n this._paused = false;\n\n if (typeof canvasSizeOrOnDecodeError === 'number') {\n // legacy function signature where canvas size is the third argument\n canvasSize = 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 this.$canvas.width = canvasSize;\n this.$canvas.height = canvasSize;\n this._sourceRect = {\n x: 0,\n y: 0,\n width: canvasSize,\n height: canvasSize\n };\n\n this._onPlay = this._onPlay.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 document.addEventListener('visibilitychange', this._onVisibilityChange);\n\n this._qrWorker = new Worker(QrScanner.WORKER_PATH);\n }\n\n destroy() {\n this.$video.removeEventListener('play', this._onPlay);\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n\n this.stop();\n this._qrWorker.postMessage({\n type: 'close'\n });\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 this._active = true;\n this._paused = false;\n if (document.hidden) {\n // camera will be started as soon as tab is in foreground\n return Promise.resolve();\n }\n clearTimeout(this._offTimeout);\n this._offTimeout = null;\n if (this.$video.srcObject) {\n // camera stream already/still set\n this.$video.play();\n return Promise.resolve();\n }\n\n let facingMode = 'environment';\n return this._getCameraStream('environment', true)\n .catch(() => {\n // we (probably) don't have an environment camera\n facingMode = 'user';\n return this._getCameraStream(); // throws if camera is not accessible (e.g. due to not https)\n })\n .then(stream => {\n if (this._isUserFacing(stream)) {\n // Safari incorrectly returns `user` facing cameras even if `environment` was specified as an exact\n // constraint. Thus check whether the stream is user facing and set the facingMode accordingly.\n facingMode = 'user';\n }\n this.$video.srcObject = stream;\n this.$video.play();\n this._setVideoMirror(facingMode);\n })\n .catch(e => {\n this._active = false;\n throw e;\n });\n }\n\n stop() {\n this.pause();\n this._active = false;\n }\n\n pause() {\n this._paused = true;\n if (!this._active) {\n return;\n }\n this.$video.pause();\n if (this._offTimeout) {\n return;\n }\n this._offTimeout = setTimeout(() => {\n const tracks = thLine truncated
|
||||
{"version":3,"file":"qr-scanner.min.js","sources":["src/qr-scanner.js"],"sourcesContent":["export default class QrScanner {\n /* async */\n static hasCamera() {\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 && 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.bind(this),\n canvasSize = QrScanner.DEFAULT_CANVAS_SIZE,\n preferredFacingMode = 'environment'\n ) {\n this.$video = video;\n this.$canvas = document.createElement('canvas');\n this._onDecode = onDecode;\n this._preferredFacingMode = preferredFacingMode;\n this._active = false;\n this._paused = false;\n\n if (typeof canvasSizeOrOnDecodeError === 'number') {\n // legacy function signature where canvas size is the third argument\n canvasSize = 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 this.$canvas.width = canvasSize;\n this.$canvas.height = canvasSize;\n this._sourceRect = {\n x: 0,\n y: 0,\n width: canvasSize,\n height: canvasSize\n };\n\n this._onPlay = this._onPlay.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 document.addEventListener('visibilitychange', this._onVisibilityChange);\n\n this._qrWorker = new Worker(QrScanner.WORKER_PATH);\n }\n\n destroy() {\n this.$video.removeEventListener('play', this._onPlay);\n document.removeEventListener('visibilitychange', this._onVisibilityChange);\n\n this.stop();\n this._qrWorker.postMessage({\n type: 'close'\n });\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 this._active = true;\n this._paused = false;\n if (document.hidden) {\n // camera will be started as soon as tab is in foreground\n return Promise.resolve();\n }\n clearTimeout(this._offTimeout);\n this._offTimeout = null;\n if (this.$video.srcObject) {\n // camera stream already/still set\n this.$video.play();\n return Promise.resolve();\n }\n\n let facingMode = this._preferredFacingMode;\n return this._getCameraStream(facingMode, true)\n .catch(() => {\n // We (probably) don't have a camera of the requested facing mode\n facingMode = facingMode === 'environment' ? 'user' : 'environment';\n return this._getCameraStream(); // throws if camera is not accessible (e.g. due to not https)\n })\n .then(stream => {\n // Try to determine the facing mode from the stream, otherwise use our guess. Note that the guess is not\n // always accurate as Safari returns cameras of different facing mode, even for exact constraints.\n facingMode = this._getFacingMode(stream) || facingMode;\n this.$video.srcObject = stream;\n this.$video.play();\n this._setVideoMirror(facingMode);\n })\n .catch(e => {\n this._active = false;\n throw e;\n });\n }\n\n stop() {\n this.pause();\n this._active = false;\n }\n\n pause() {\n this._paused = true;\n if (!this._active) {\n return;\n }\n this.$video.pause();\n Line truncated
|
||||
+17
-13
@@ -13,11 +13,13 @@ export default class QrScanner {
|
||||
video,
|
||||
onDecode,
|
||||
canvasSizeOrOnDecodeError = this._onDecodeError.bind(this),
|
||||
canvasSize = QrScanner.DEFAULT_CANVAS_SIZE
|
||||
canvasSize = QrScanner.DEFAULT_CANVAS_SIZE,
|
||||
preferredFacingMode = 'environment'
|
||||
) {
|
||||
this.$video = video;
|
||||
this.$canvas = document.createElement('canvas');
|
||||
this._onDecode = onDecode;
|
||||
this._preferredFacingMode = preferredFacingMode;
|
||||
this._active = false;
|
||||
this._paused = false;
|
||||
|
||||
@@ -88,19 +90,17 @@ export default class QrScanner {
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
let facingMode = 'environment';
|
||||
return this._getCameraStream('environment', true)
|
||||
let facingMode = this._preferredFacingMode;
|
||||
return this._getCameraStream(facingMode, true)
|
||||
.catch(() => {
|
||||
// we (probably) don't have an environment camera
|
||||
facingMode = 'user';
|
||||
// We (probably) don't have a camera of the requested facing mode
|
||||
facingMode = facingMode === 'environment' ? 'user' : 'environment';
|
||||
return this._getCameraStream(); // throws if camera is not accessible (e.g. due to not https)
|
||||
})
|
||||
.then(stream => {
|
||||
if (this._isUserFacing(stream)) {
|
||||
// Safari incorrectly returns `user` facing cameras even if `environment` was specified as an exact
|
||||
// constraint. Thus check whether the stream is user facing and set the facingMode accordingly.
|
||||
facingMode = 'user';
|
||||
}
|
||||
// Try to determine the facing mode from the stream, otherwise use our guess. Note that the guess is not
|
||||
// always accurate as Safari returns cameras of different facing mode, even for exact constraints.
|
||||
facingMode = this._getFacingMode(stream) || facingMode;
|
||||
this.$video.srcObject = stream;
|
||||
this.$video.play();
|
||||
this._setVideoMirror(facingMode);
|
||||
@@ -285,11 +285,15 @@ export default class QrScanner {
|
||||
this.$video.style.transform = 'scaleX(' + scaleFactor + ')';
|
||||
}
|
||||
|
||||
_isUserFacing(videoStream) {
|
||||
_getFacingMode(videoStream) {
|
||||
const videoTrack = videoStream.getVideoTracks()[0];
|
||||
if (!videoTrack) return false; // assume false
|
||||
if (!videoTrack) return null; // unknown
|
||||
// inspired by https://github.com/JodusNodus/react-qr-reader/blob/master/src/getDeviceId.js#L13
|
||||
return /front|user|face/i.test(videoTrack.label);
|
||||
return /rear|back|environment/i.test(videoTrack.label)
|
||||
? 'environment'
|
||||
: /front|user|face/i.test(videoTrack.label)
|
||||
? 'user'
|
||||
: null; // unknown
|
||||
}
|
||||
|
||||
static _getImageData(image, sourceRect=null, canvas=null, fixedCanvasSize=false) {
|
||||
|
||||
Vendored
+1
@@ -14,6 +14,7 @@ declare class QrScanner {
|
||||
onDecode: (result: string) => void,
|
||||
onDecodeError?: (error: string) => void,
|
||||
canvasSize?: number,
|
||||
preferredFacingMode?: 'environment' | 'user',
|
||||
);
|
||||
/** @deprecated */
|
||||
constructor(video: HTMLVideoElement, onDecode: (result: string) => void, canvasSize?: number);
|
||||
|
||||
Reference in new issue
Block a user