video-auth-js-sdk
Version:
A SDK to authenticate users with camera through a realtime stream
372 lines (371 loc) • 19 kB
JavaScript
"use strict";
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
var _typeof = require("@babel/runtime/helpers/typeof");
Object.defineProperty(exports, "__esModule", {
value: true
});
exports["default"] = void 0;
var _regenerator = _interopRequireDefault(require("@babel/runtime/regenerator"));
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
var _asyncToGenerator2 = _interopRequireDefault(require("@babel/runtime/helpers/asyncToGenerator"));
var _classCallCheck2 = _interopRequireDefault(require("@babel/runtime/helpers/classCallCheck"));
var _createClass2 = _interopRequireDefault(require("@babel/runtime/helpers/createClass"));
var faceapi = _interopRequireWildcard(require("@vladmandic/face-api"));
var _errorHandler = require("./errorHandler");
function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(e) { return e ? t : r; })(e); }
function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != _typeof(e) && "function" != typeof e) return { "default": e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n["default"] = e, t && t.set(e, n), n; }
function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { (0, _defineProperty2["default"])(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it["return"] != null) it["return"](); } finally { if (didErr) throw err; } } }; }
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
var FaceAPI = /*#__PURE__*/function () {
function FaceAPI(app) {
(0, _classCallCheck2["default"])(this, FaceAPI);
this._app = app;
this.optionsSSDMobileNet = null;
this.minScore = 0.2; // minimum score
this.maxResults = 5; // maximum number of results to return
this.modelPath = app.params.modelPath; // path to model folder that will be loaded using http
this._isDistroyed = false;
}
return (0, _createClass2["default"])(FaceAPI, [{
key: "detectIsFaceCorrect",
value: function () {
var _detectIsFaceCorrect = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee(callback) {
return _regenerator["default"].wrap(function _callee$(_context) {
while (1) switch (_context.prev = _context.next) {
case 0:
_context.next = 2;
return this.main();
case 2:
case "end":
return _context.stop();
}
}, _callee, this);
}));
function detectIsFaceCorrect(_x) {
return _detectIsFaceCorrect.apply(this, arguments);
}
return detectIsFaceCorrect;
}()
}, {
key: "drawFaces",
value: function drawFaces(canvas, data, fps) {
if (!data.length) {
this._app.store.faceDetected = false;
}
var ctx = canvas.getContext('2d', {
willReadFrequently: true
});
if (!ctx) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// draw title
ctx.font = 'small-caps 20px "Segoe UI"';
ctx.fillStyle = 'white';
ctx.fillText("FPS: ".concat(fps), 10, 25);
var _iterator = _createForOfIteratorHelper(data),
_step;
try {
for (_iterator.s(); !(_step = _iterator.n()).done;) {
var person = _step.value;
// draw box around each face
ctx.lineWidth = 3;
ctx.strokeStyle = 'deepskyblue';
ctx.fillStyle = 'deepskyblue';
ctx.globalAlpha = 0.6;
ctx.beginPath();
var ellipseBoxPosCenter = {
x: parseInt(canvas.width / 2),
y: parseInt(canvas.height / 2),
radiusX: 70,
radiusY: 100
};
if (person.detection.box.x + person.detection.box.width / 2 < ellipseBoxPosCenter.x + 20 && person.detection.box.x + person.detection.box.width / 2 > ellipseBoxPosCenter.x - 20 && person.detection.box.y + person.detection.box.height / 2 > ellipseBoxPosCenter.y - 20 && person.detection.box.y + person.detection.box.height / 2 < ellipseBoxPosCenter.y + 20 && person.angle.yaw > -20 && person.angle.yaw < 20 && person.angle.pitch > -15 && person.angle.pitch < 15) {
this._app.store.faceDetected = true;
ctx.strokeStyle = 'green';
} else {
ctx.strokeStyle = 'red';
this._app.store.faceDetected = false;
}
ctx.ellipse(ellipseBoxPosCenter.x, ellipseBoxPosCenter.y, ellipseBoxPosCenter.radiusX, ellipseBoxPosCenter.radiusY, Math.PI, 0, 2 * Math.PI);
ctx.stroke();
}
} catch (err) {
_iterator.e(err);
} finally {
_iterator.f();
}
}
}, {
key: "detectVideo",
value: function () {
var _detectVideo = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee2(video, canvas) {
var _this = this;
var t0;
return _regenerator["default"].wrap(function _callee2$(_context2) {
while (1) switch (_context2.prev = _context2.next) {
case 0:
if (!(!video || video.paused || this._isDistroyed)) {
_context2.next = 2;
break;
}
return _context2.abrupt("return", false);
case 2:
t0 = performance.now();
faceapi.detectAllFaces(video, this.optionsSSDMobileNet).withFaceLandmarks().withFaceExpressions()
// .withFaceDescriptors()
.withAgeAndGender().then(function (result) {
var fps = 1000 / (performance.now() - t0);
var resizedDetections = faceapi.resizeResults(result, {
width: canvas.width,
height: canvas.height
});
_this.drawFaces(canvas, resizedDetections, fps.toLocaleString());
setTimeout(function () {
requestAnimationFrame(function () {
return _this.detectVideo(video, canvas);
});
}, 1000);
return true;
})["catch"](function (err) {
console.log("Detect Error: ".concat(err));
return false;
});
return _context2.abrupt("return", false);
case 5:
case "end":
return _context2.stop();
}
}, _callee2, this);
}));
function detectVideo(_x2, _x3) {
return _detectVideo.apply(this, arguments);
}
return detectVideo;
}() // just initialize everything and call main function
}, {
key: "setupCamera",
value: function () {
var _setupCamera = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee4() {
var _this2 = this;
var video, canvas, constraints, track, settings;
return _regenerator["default"].wrap(function _callee4$(_context4) {
while (1) switch (_context4.prev = _context4.next) {
case 0:
video = this._app.store.videoTag;
canvas = this._app.store.canvasTag;
if (!(!video || !canvas)) {
_context4.next = 4;
break;
}
return _context4.abrupt("return", null);
case 4:
if (navigator.mediaDevices) {
_context4.next = 8;
break;
}
console.error('Camera Error: access not supported');
this._app.publicCallbacks.onError(_errorHandler.errorList.MEDIA_DEVICES_NOT_SUPPORTED);
return _context4.abrupt("return", null);
case 8:
constraints = {
audio: false,
video: {
facingMode: 'user',
exact: "environment",
resizeMode: 'crop-and-scale'
}
};
if (window.innerWidth > window.innerHeight) constraints.video.width = {
ideal: window.innerWidth
}; //width: 1920, height: 1280 }//ideal: 1280}//window.innerWidth };
else constraints.video.height = {
ideal: window.innerHeight
};
_context4.prev = 10;
_context4.next = 13;
return navigator.mediaDevices.getUserMedia(constraints);
case 13:
this._app.store.localCameraStream = _context4.sent;
_context4.next = 21;
break;
case 16:
_context4.prev = 16;
_context4.t0 = _context4["catch"](10);
if (_context4.t0.name === 'PermissionDeniedError' || _context4.t0.name === 'NotAllowedError') {
console.error("Camera Error: camera permission denied: ".concat(_context4.t0.message || _context4.t0));
this._app.publicCallbacks.onError(_errorHandler.errorList.VIDEO_PERMISSION_ERROR);
}
if (_context4.t0.name === 'SourceUnavailableError') {
console.error("Camera Error: camera not available: ".concat(_context4.t0.message || _context4.t0));
this._app.publicCallbacks.onError(this._app.errorHandler.getFilledErrorObject(_objectSpread(_objectSpread({}, _errorHandler.errorList.CAMERA_NOT_AVAILABLE), {}, {
replacements: [_context4.t0.message || _context4.t0]
})));
}
return _context4.abrupt("return", null);
case 21:
if (!this._app.store.localCameraStream) {
_context4.next = 25;
break;
}
video.srcObject = this._app.store.localCameraStream;
_context4.next = 27;
break;
case 25:
console.error('Camera Error: stream empty');
return _context4.abrupt("return", null);
case 27:
track = this._app.store.localCameraStream.getVideoTracks()[0];
settings = track.getSettings();
if (settings.deviceId) delete settings.deviceId;
if (settings.groupId) delete settings.groupId;
// if (settings.aspectRatio) settings.aspectRatio = Math.trunc(100 * settings.aspectRatio) / 100;
console.log("Camera active: ".concat(track.label));
console.log("Camera settings: ", {
settings: settings
});
canvas.addEventListener('click', function () {
if (video && video.readyState >= 2) {
if (video.paused) {
video.play();
_this2.detectVideo(video, canvas);
} else {
video.pause();
}
}
console.log("Camera state: ".concat(video.paused ? 'paused' : 'playing'));
});
return _context4.abrupt("return", new Promise(function (resolve) {
video.onloadeddata = /*#__PURE__*/(0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee3() {
var base, aspect;
return _regenerator["default"].wrap(function _callee3$(_context3) {
while (1) switch (_context3.prev = _context3.next) {
case 0:
base = "videoWidth";
if (video.videoWidth < video.videoHeight) {
base = "videoHeight";
}
aspect = video.videoWidth / video.videoHeight;
if (base == "videoWidth") {
canvas.width = video.offsetWidth;
canvas.height = parseInt(video.offsetWidth / aspect);
canvas.style.left = '0px';
canvas.style.top = parseInt((video.offsetHeight - canvas.height) / 2) + 'px';
} else {
canvas.height = video.offsetHeight;
canvas.width = parseInt(video.offsetHeight / aspect);
canvas.style.top = '0px';
canvas.style.left = parseInt((video.offsetWidth - canvas.width) / 2) + 'px';
}
setTimeout(function () {
video.play();
_this2.detectVideo(video, canvas);
resolve(true);
});
case 5:
case "end":
return _context3.stop();
}
}, _callee3);
}));
}));
case 35:
case "end":
return _context4.stop();
}
}, _callee4, this, [[10, 16]]);
}));
function setupCamera() {
return _setupCamera.apply(this, arguments);
}
return setupCamera;
}()
}, {
key: "setupFaceAPI",
value: function () {
var _setupFaceAPI = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee5() {
return _regenerator["default"].wrap(function _callee5$(_context5) {
while (1) switch (_context5.prev = _context5.next) {
case 0:
_context5.next = 2;
return faceapi.nets.ssdMobilenetv1.load(this.modelPath);
case 2:
_context5.next = 4;
return faceapi.nets.ageGenderNet.load(this.modelPath);
case 4:
_context5.next = 6;
return faceapi.nets.faceLandmark68Net.load(this.modelPath);
case 6:
_context5.next = 8;
return faceapi.nets.faceRecognitionNet.load(this.modelPath);
case 8:
_context5.next = 10;
return faceapi.nets.faceExpressionNet.load(this.modelPath);
case 10:
this.optionsSSDMobileNet = new faceapi.SsdMobilenetv1Options({
minConfidence: this.minScore,
maxResults: this.maxResults
});
case 11:
case "end":
return _context5.stop();
}
}, _callee5, this);
}));
function setupFaceAPI() {
return _setupFaceAPI.apply(this, arguments);
}
return setupFaceAPI;
}()
}, {
key: "main",
value: function () {
var _main = (0, _asyncToGenerator2["default"])( /*#__PURE__*/_regenerator["default"].mark(function _callee6() {
var _faceapi$tf, _faceapi$tf2, _faceapi$tf3;
return _regenerator["default"].wrap(function _callee6$(_context6) {
while (1) switch (_context6.prev = _context6.next) {
case 0:
_context6.next = 2;
return faceapi.tf.setBackend('webgl');
case 2:
_context6.next = 4;
return faceapi.tf.ready();
case 4:
if ((_faceapi$tf = faceapi.tf) !== null && _faceapi$tf !== void 0 && _faceapi$tf.env().flagRegistry.CANVAS2D_WILL_READ_FREQUENTLY) faceapi.tf.env().set('CANVAS2D_WILL_READ_FREQUENTLY', true);
if ((_faceapi$tf2 = faceapi.tf) !== null && _faceapi$tf2 !== void 0 && _faceapi$tf2.env().flagRegistry.WEBGL_EXP_CONV) faceapi.tf.env().set('WEBGL_EXP_CONV', true);
if ((_faceapi$tf3 = faceapi.tf) !== null && _faceapi$tf3 !== void 0 && _faceapi$tf3.env().flagRegistry.WEBGL_EXP_CONV) faceapi.tf.env().set('WEBGL_EXP_CONV', true);
_context6.next = 9;
return this.setupFaceAPI();
case 9:
_context6.next = 11;
return this.setupCamera();
case 11:
case "end":
return _context6.stop();
}
}, _callee6, this);
}));
function main() {
return _main.apply(this, arguments);
}
return main;
}()
}, {
key: "destroy",
value: function destroy() {
this._isDistroyed = true;
if (this._app.store.videoParentTag) {
this._app.store.videoParentTag.remove();
this._app.store.videoParentTag = null;
}
if (this._app.store.localCameraStream) {
this._app.store.localCameraStream.getTracks().forEach(function (item) {
return item.stop();
});
this._app.store.localCameraStream = null;
}
}
}]);
}();
var _default = exports["default"] = FaceAPI;