Skip to content

Commit

Permalink
Merge pull request #104 from mebjas/facingMode
Browse files Browse the repository at this point in the history
Add support for facingMode constraint
  • Loading branch information
mebjas authored Aug 30, 2020
2 parents cee168f + 00da52e commit 5b8366b
Show file tree
Hide file tree
Showing 10 changed files with 500 additions and 44 deletions.
47 changes: 46 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ You can use the following APIs to `fetch camera`, `start` scanning and `stop` sc

#### For using inline QR Code scanning with Webcam or Smartphone camera

##### Start Scanning
To get a list of supported cameras, query it using static method `Html5Qrcode.getCameras()`. This method returns a `Promise` with a list of devices supported in format `{ id: "id", label: "label" }`.
```js
// This method will trigger user permissions
Expand Down Expand Up @@ -159,6 +160,33 @@ html5QrCode.start(
> const html5QrCode = new Html5Qrcode("reader", /* verbose= */ true);
> ```
In mobile devices you may want users to directly scan the QR code using the back camera or the front camera for some use cases. For such cases you can avoid using the exact camera device id that you get from `Html5Qrcode.getCameras()`. The `start()` method allows passing constraints in place of camera device id similar to [html5 web API syntax](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Syntax). You can start scanning like mentioned in these examples:
```js
const html5QrCode = new Html5Qrcode("#reader");
const qrCodeSuccessCallback = message => { /* handle success */ }
const config = { fps: 10, qrbox: 250 };
// If you want to prefer front camera
html5QrCode.start({ facingMode: "user" }, config, qrCodeSuccessCallback);
// If you want to prefer back camera
html5QrCode.start({ facingMode: "environment" }, config, qrCodeSuccessCallback);
// Select front camera or fail with `OverconstrainedError`.
html5QrCode.start({ facingMode: { exact: "user"} }, config, qrCodeSuccessCallback);
// Select back camera or fail with `OverconstrainedError`.
html5QrCode.start({ facingMode: { exact: "environment"} }, config, qrCodeSuccessCallback);
```
Passing the `cameraId` (recommended appraoch) is similar to
```js
html5QrCode.start({ deviceId: { exact: cameraId} }, config, qrCodeSuccessCallback);
```

##### Stop Scanning

To stop using camera and thus stop scanning, call `Html5Qrcode#stop()` which returns a `Promise` for stopping the video feed and scanning.
```js
html5QrCode.stop().then(ignore => {
Expand Down Expand Up @@ -263,7 +291,20 @@ class Html5Qrcode {
/**
* Start scanning QR Code for given camera.
*
* @param {String} cameraId Id of the camera to use.
* @param {String or Object} identifier of the camera, it can either be the
* cameraId retrieved from {@code Html5Qrcode#getCameras()} method or
* object with facingMode constraint.
* Example values:
* - "a76afe74e951cde2d3e29aa73065c9cd89438627b3bde"
* ^ This is 'deviceId' from camera retrieved from
* {@code Html5Qrcode#getCameras()}
* - { facingMode: "user" }
* - { facingMode: "environment" }
* - { facingMode: { exact: "environment" } }
* - { facingMode: { exact: "user" } }
* - { deviceId: { exact: "a76afe74e95e3....73065c9cd89438627b3bde" }
* - { deviceId: "a76afe74e95e3....73065c9cd89438627b3bde" }
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Syntax
* @param {Object} config extra configurations to tune QR code scanner.
* Supported Fields:
* - fps: expected framerate of qr code scanning. example { fps: 2 }
Expand Down Expand Up @@ -429,6 +470,10 @@ Here's an example of normal and mirrored QR Code
- [third_party/qrcode.js](./third_party/qrcode.js)
2. Run `npm run-script build`.
> This should do `transpiling` --> `minification` --> `merging` different js code.
3. Testing
- Run `npm test`
- Run the tests before sending PR, all tests should run.
- Please add tests for new behaviors sent in PR.

> Before sending a pull request with changes to [html5-qrcode.js](./html5-qrcode.js) please run instruction (2).
Expand Down
24 changes: 24 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,27 @@
### Version 1.2.1
+ Added support for `facingMode` constraing in `Html5Qrcode#start`

**Update**:
In mobile devices you may want users to directly scan the QR code using the back camera or the front camera for some use cases. For such cases you can avoid using the exact camera device id that you get from `Html5Qrcode.getCameras()`. The `start()` method allows passing constraints in place of camera device id similar to [html5 web API syntax](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Syntax). You can start scanning like mentioned in these examples:

```js
const html5QrCode = new Html5Qrcode("#reader");
const qrCodeSuccessCallback = message => { /* handle success */ }
const config = { fps: 10, qrbox: 250 };

// If you want to prefer front camera
html5QrCode.start({ facingMode: "user" }, config, qrCodeSuccessCallback);

// If you want to prefer back camera
html5QrCode.start({ facingMode: "environment" }, config, qrCodeSuccessCallback);

// Select front camera or fail with `OverconstrainedError`.
html5QrCode.start({ facingMode: { exact: "user"} }, config, qrCodeSuccessCallback);

// Select back camera or fail with `OverconstrainedError`.
html5QrCode.start({ facingMode: { exact: "environment"} }, config, qrCodeSuccessCallback);
```

### Version 1.2.0
+ Added support for scanning mirrored QR code, or scanning in case camera feed is mirrored (horizontally flipped).

Expand Down
134 changes: 124 additions & 10 deletions html5-qrcode.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,20 @@ class Html5Qrcode {
/**
* Start scanning QR Code for given camera.
*
* @param {String} cameraId Id of the camera to use.
* @param {String or Object} identifier of the camera, it can either be the
* cameraId retrieved from {@code Html5Qrcode#getCameras()} method or
* object with facingMode constraint.
* Example values:
* - "a76afe74e95e3aba9fc1b69c39b8701cde2d3e29aa73065c9cd89438627b3bde"
* ^ This is 'deviceId' from camera retrieved from
* {@code Html5Qrcode#getCameras()}
* - { facingMode: "user" }
* - { facingMode: "environment" }
* - { facingMode: { exact: "environment" } }
* - { facingMode: { exact: "user" } }
* - { deviceId: { exact: "a76afe74e95e3....73065c9cd89438627b3bde" }
* - { deviceId: "a76afe74e95e3....73065c9cd89438627b3bde" }
* Reference: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Syntax
* @param {Object} config extra configurations to tune QR code scanner.
* Supported Fields:
* - fps: expected framerate of qr code scanning. example { fps: 2 }
Expand Down Expand Up @@ -97,12 +110,12 @@ class Html5Qrcode {
* @returns Promise for starting the scan. The Promise can fail if the user
* doesn't grant permission or some API is not supported by the browser.
*/
start(cameraId,
start(cameraIdOrConfig,
configuration,
qrCodeSuccessCallback,
qrCodeErrorCallback) {
if (!cameraId) {
throw "cameraId is required";
if (!cameraIdOrConfig) {
throw "cameraIdOrConfig is required";
}

if (!qrCodeSuccessCallback
Expand Down Expand Up @@ -307,12 +320,13 @@ class Html5Qrcode {

return new Promise((resolve, reject) => {
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
const videoConstraints = {
deviceId: { exact: cameraId }
};
const videoConstraints = $this._createVideoConstraints(
cameraIdOrConfig);
navigator.mediaDevices.getUserMedia(
{ audio: false, video: videoConstraints })
.then(stream => {
{
audio: false,
video: videoConstraints
}).then(stream => {
onMediaStreamReceived(stream)
.then(_ => {
$this._isScanning = true;
Expand All @@ -324,10 +338,15 @@ class Html5Qrcode {
reject(`Error getting userMedia, error = ${err}`);
});
} else if (navigator.getUserMedia) {
if (typeof cameraIdOrConfig != "string") {
throw "The device doesn't support navigator.mediaDevices"
+ ", only supported cameraIdOrConfig in this case is"
+ " deviceId parameter (string)."
}
const getCameraConfig = {
video: {
optional: [{
sourceId: cameraId
sourceId: cameraIdOrConfig
}]
}
};
Expand Down Expand Up @@ -849,6 +868,101 @@ class Html5Qrcode {
}
}

//#region private method to create correct camera selection filter.
_createVideoConstraints(cameraIdOrConfig) {
if (typeof cameraIdOrConfig == "string") {
// If it's a string it should be camera device Id.
return { deviceId: { exact: cameraIdOrConfig } };
} else if (typeof cameraIdOrConfig == "object") {
const facingModeKey = "facingMode";
const deviceIdKey = "deviceId";
const allowedFacingModeValues
= { "user" : true, "environment" : true};
const exactKey = "exact";
const isValidFacingModeValue = value => {
if (value in allowedFacingModeValues) {
// Valid config
return true;
} else {
// Invalid config
throw "config has invalid 'facingMode' value = "
+ `'${value}'`;
}
};

const keys = Object.keys(cameraIdOrConfig);
if (keys.length != 1) {
throw "'cameraIdOrConfig' object should have exactly 1 key,"
+ ` if passed as an object, found ${keys.length} keys`;
}

const key = Object.keys(cameraIdOrConfig)[0];
if (key != facingModeKey && key != deviceIdKey) {
throw `Only '${facingModeKey}' and '${deviceIdKey}' `
+ " are supported for 'cameraIdOrConfig'";
}

if (key == facingModeKey) {
/**
* Supported scenarios:
* - { facingMode: "user" }
* - { facingMode: "environment" }
* - { facingMode: { exact: "environment" } }
* - { facingMode: { exact: "user" } }
*/
const facingMode = cameraIdOrConfig[key];
if (typeof facingMode == "string") {
if (isValidFacingModeValue(facingMode)) {
return { facingMode: facingMode };
}
} else if (typeof facingMode == "object") {
if (exactKey in facingMode) {
if (isValidFacingModeValue(facingMode[exactKey])) {
return {
facingMode: {
exact: facingMode[exactKey]
}
};
}
} else {
throw "'facingMode' should be string or object with"
+ ` ${exactKey} as key.`;
}
} else {
const type = (typeof facingMode);
throw `Invalid type of 'facingMode' = ${type}`;
}
} else {
/**
* key == deviceIdKey; Supported scenarios:
* - { deviceId: { exact: "a76afe74e95e3.....38627b3bde" }
* - { deviceId: "a76afe74e95e3....065c9cd89438627b3bde" }
*/
const deviceId = cameraIdOrConfig[key];
if (typeof deviceId == "string") {
return { deviceId: deviceId };
} else if (typeof deviceId == "object") {
if (exactKey in deviceId) {
return {
deviceId : { exact: deviceId[exactKey] }
};
} else {
throw "'deviceId' should be string or object with"
+ ` ${exactKey} as key.`;
}
} else {
const type = (typeof deviceId);
throw `Invalid type of 'deviceId' = ${type}`;
}
}
} else {
// invalid type
const type = (typeof cameraIdOrConfig);
throw `Invalid type of 'cameraIdOrConfig' = ${type}`;
}
}
//#endregion

static _getTimeoutFps(fps) {
return 1000 / fps;
}
Expand Down
4 changes: 4 additions & 0 deletions minified/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Generated code, do not modify manually.

- All files in this directory are minified from source code in the project.
- Generated files should be supported in all major browsers.
2 changes: 1 addition & 1 deletion minified/html5-qrcode.min.js

Large diffs are not rendered by default.

16 changes: 9 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "html5-qrcode",
"version": "1.2.0",
"version": "1.2.1",
"description": "a cross platform HTML5 QR Code scanner",
"main": "html5-qrcode.js",
"scripts": {
Expand All @@ -27,15 +27,17 @@
},
"homepage": "https://github.com/mebjas/html5-qrcode#readme",
"devDependencies": {
"@babel/cli": "^7.8.4",
"@babel/core": "^7.9.0",
"@babel/preset-env": "^7.9.5",
"@babel/cli": "^7.10.5",
"@babel/core": "^7.11.4",
"@babel/plugin-proposal-class-properties": "^7.10.4",
"@babel/preset-env": "^7.11.0",
"babel-minify": "^0.5.1",
"mocha": "^7.1.2",
"chai": "^4.2.0",
"mocha": "^7.2.0",
"mocha-phantomjs": "^4.1.0",
"phantomjs": "^2.1.7",
"rewire": "^5.0.0",
"@babel/plugin-proposal-class-properties": "^7.8.3"
"promise-polyfill": "^8.1.3",
"rewire": "^5.0.0"
},
"files": [
"minified/*"
Expand Down
13 changes: 9 additions & 4 deletions test/test.html5-qrcode.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
<meta charset="utf-8" />
<title>Mocha Tests</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="https://unpkg.com/mocha/mocha.css" />
<link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
</head>
<body>
<div id="mocha"></div>
Expand All @@ -13,16 +13,21 @@
<!-- TODO(mebjas): Can this be hidden? -->
<div id="qr"></div>

<!-- Polyfills -->
<script src="../node_modules/promise-polyfill/dist/polyfill.min.js"></script>

<!-- actual library code -->
<script src="../third_party/qrcode.min.js"></script>
<script src="../transpiled/html5-qrcode.js"></script>

<!-- test dependencies -->
<script src="https://unpkg.com/chai/chai.js"></script>
<script src="https://unpkg.com/mocha/mocha.js"></script>
<script class="mocha-init">
<script src="../node_modules/mocha/mocha.js"></script>
<script src="../node_modules/chai/chai.js"></script>
<script>
mocha.setup('bdd');
mocha.reporter('html');
mocha.checkLeaks();
const expect = chai.expect;
const assert = chai.assert;
</script>

Expand Down
Loading

0 comments on commit 5b8366b

Please sign in to comment.