UNPKG

instascan-last

Version:

Webcam-driven QR code scanner.

578 lines (480 loc) 16.1 kB
'use strict'; var _regenerator = require('babel-runtime/regenerator'); var _regenerator2 = _interopRequireDefault(_regenerator); var _asyncToGenerator2 = require('babel-runtime/helpers/asyncToGenerator'); var _asyncToGenerator3 = _interopRequireDefault(_asyncToGenerator2); var _getPrototypeOf = require('babel-runtime/core-js/object/get-prototype-of'); var _getPrototypeOf2 = _interopRequireDefault(_getPrototypeOf); var _possibleConstructorReturn2 = require('babel-runtime/helpers/possibleConstructorReturn'); var _possibleConstructorReturn3 = _interopRequireDefault(_possibleConstructorReturn2); var _inherits2 = require('babel-runtime/helpers/inherits'); var _inherits3 = _interopRequireDefault(_inherits2); var _trunc = require('babel-runtime/core-js/math/trunc'); var _trunc2 = _interopRequireDefault(_trunc); var _classCallCheck2 = require('babel-runtime/helpers/classCallCheck'); var _classCallCheck3 = _interopRequireDefault(_classCallCheck2); var _createClass2 = require('babel-runtime/helpers/createClass'); var _createClass3 = _interopRequireDefault(_createClass2); function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } var EventEmitter = require('events'); var ZXing = require('./vendor/zxing')(); var Visibility = require('visibilityjs'); var StateMachine = require('fsm-as-promised'); var ScanProvider = function () { function ScanProvider(emitter, analyzer, captureImage, scanPeriod, refractoryPeriod) { (0, _classCallCheck3.default)(this, ScanProvider); this.scanPeriod = scanPeriod; this.captureImage = captureImage; this.refractoryPeriod = refractoryPeriod; this._emitter = emitter; this._frameCount = 0; this._analyzer = analyzer; this._lastResult = null; this._active = false; } (0, _createClass3.default)(ScanProvider, [{ key: 'start', value: function start() { var _this = this; this._active = true; requestAnimationFrame(function () { return _this._scan(); }); } }, { key: 'stop', value: function stop() { this._active = false; } }, { key: 'scan', value: function scan() { return this._analyze(false); } }, { key: '_analyze', value: function _analyze(skipDups) { var _this2 = this; var analysis = this._analyzer.analyze(); if (!analysis) { return null; } var result = analysis.result, canvas = analysis.canvas; if (!result) { return null; } if (skipDups && result === this._lastResult) { return null; } clearTimeout(this.refractoryTimeout); this.refractoryTimeout = setTimeout(function () { _this2._lastResult = null; }, this.refractoryPeriod); var image = this.captureImage ? canvas.toDataURL('image/webp', 0.8) : null; this._lastResult = result; var payload = { content: result }; if (image) { payload.image = image; } return payload; } }, { key: '_scan', value: function _scan() { var _this3 = this; if (!this._active) { return; } requestAnimationFrame(function () { return _this3._scan(); }); if (++this._frameCount !== this.scanPeriod) { return; } else { this._frameCount = 0; } var result = this._analyze(true); if (result) { setTimeout(function () { _this3._emitter.emit('scan', result.content, result.image || null); }, 0); } } }]); return ScanProvider; }(); var Analyzer = function () { function Analyzer(video) { (0, _classCallCheck3.default)(this, Analyzer); this.video = video; this.imageBuffer = null; this.sensorLeft = null; this.sensorTop = null; this.sensorWidth = null; this.sensorHeight = null; this.canvas = document.createElement('canvas'); this.canvas.style.display = 'none'; this.canvasContext = null; this.decodeCallback = ZXing.Runtime.addFunction(function (ptr, len, resultIndex, resultCount) { var result = new Uint8Array(ZXing.HEAPU8.buffer, ptr, len); var str = String.fromCharCode.apply(null, result); if (resultIndex === 0) { window.zxDecodeResult = ''; } window.zxDecodeResult += str; }); } (0, _createClass3.default)(Analyzer, [{ key: 'analyze', value: function analyze() { if (!this.video.videoWidth) { return null; } if (!this.imageBuffer) { var videoWidth = this.video.videoWidth; var videoHeight = this.video.videoHeight; this.sensorWidth = videoWidth; this.sensorHeight = videoHeight; this.sensorLeft = Math.floor(videoWidth / 2 - this.sensorWidth / 2); this.sensorTop = Math.floor(videoHeight / 2 - this.sensorHeight / 2); this.canvas.width = this.sensorWidth; this.canvas.height = this.sensorHeight; this.canvasContext = this.canvas.getContext('2d'); this.imageBuffer = ZXing._resize(this.sensorWidth, this.sensorHeight); return null; } this.canvasContext.drawImage(this.video, this.sensorLeft, this.sensorTop, this.sensorWidth, this.sensorHeight); var data = this.canvasContext.getImageData(0, 0, this.sensorWidth, this.sensorHeight).data; for (var i = 0, j = 0; i < data.length; i += 4, j++) { var _ref = [data[i], data[i + 1], data[i + 2]], r = _ref[0], g = _ref[1], b = _ref[2]; ZXing.HEAPU8[this.imageBuffer + j] = (0, _trunc2.default)((r + g + b) / 3); } var err = ZXing._decode_qr(this.decodeCallback); if (err) { return null; } var result = window.zxDecodeResult; if (result != null) { return { result: result, canvas: this.canvas }; } return null; } }]); return Analyzer; }(); var Scanner = function (_EventEmitter) { (0, _inherits3.default)(Scanner, _EventEmitter); function Scanner(opts) { (0, _classCallCheck3.default)(this, Scanner); var _this4 = (0, _possibleConstructorReturn3.default)(this, (Scanner.__proto__ || (0, _getPrototypeOf2.default)(Scanner)).call(this)); _this4.video = _this4._configureVideo(opts); _this4.mirror = opts.mirror !== false; _this4.backgroundScan = opts.backgroundScan !== false; _this4._continuous = opts.continuous !== false; _this4._analyzer = new Analyzer(_this4.video); _this4._camera = null; var captureImage = opts.captureImage || false; var scanPeriod = opts.scanPeriod || 1; var refractoryPeriod = opts.refractoryPeriod || 5 * 1000; _this4._scanner = new ScanProvider(_this4, _this4._analyzer, captureImage, scanPeriod, refractoryPeriod); _this4._fsm = _this4._createStateMachine(); Visibility.change(function (e, state) { if (state === 'visible') { setTimeout(function () { if (_this4._fsm.can('activate')) { _this4._fsm.activate(); } }, 0); } else { if (!_this4.backgroundScan && _this4._fsm.can('deactivate')) { _this4._fsm.deactivate(); } } }); _this4.addListener('active', function () { _this4.video.classList.remove('inactive'); _this4.video.classList.add('active'); }); _this4.addListener('inactive', function () { _this4.video.classList.remove('active'); _this4.video.classList.add('inactive'); }); _this4.emit('inactive'); return _this4; } (0, _createClass3.default)(Scanner, [{ key: 'scan', value: function scan() { return this._scanner.scan(); } }, { key: 'start', value: function () { var _ref2 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee() { var camera = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null; return _regenerator2.default.wrap(function _callee$(_context) { while (1) { switch (_context.prev = _context.next) { case 0: if (!this._fsm.can('start')) { _context.next = 5; break; } _context.next = 3; return this._fsm.start(camera); case 3: _context.next = 9; break; case 5: _context.next = 7; return this._fsm.stop(); case 7: _context.next = 9; return this._fsm.start(camera); case 9: case 'end': return _context.stop(); } } }, _callee, this); })); function start() { return _ref2.apply(this, arguments); } return start; }() }, { key: 'stop', value: function () { var _ref3 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee2() { return _regenerator2.default.wrap(function _callee2$(_context2) { while (1) { switch (_context2.prev = _context2.next) { case 0: if (!this._fsm.can('stop')) { _context2.next = 3; break; } _context2.next = 3; return this._fsm.stop(); case 3: case 'end': return _context2.stop(); } } }, _callee2, this); })); function stop() { return _ref3.apply(this, arguments); } return stop; }() }, { key: '_enableScan', value: function () { var _ref4 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee3(camera) { var stream; return _regenerator2.default.wrap(function _callee3$(_context3) { while (1) { switch (_context3.prev = _context3.next) { case 0: this._camera = camera || this._camera; if (this._camera) { _context3.next = 3; break; } throw new Error('Camera is not defined.'); case 3: _context3.next = 5; return this._camera.start(); case 5: stream = _context3.sent; this.video.srcObject = stream; if (this._continuous) { this._scanner.start(); } case 8: case 'end': return _context3.stop(); } } }, _callee3, this); })); function _enableScan(_x2) { return _ref4.apply(this, arguments); } return _enableScan; }() }, { key: '_disableScan', value: function _disableScan() { this.video.src = ''; if (this._scanner) { this._scanner.stop(); } if (this._camera) { this._camera.stop(); } } }, { key: '_configureVideo', value: function _configureVideo(opts) { if (opts.video) { if (opts.video.tagName !== 'VIDEO') { throw new Error('Video must be a <video> element.'); } } var video = opts.video || document.createElement('video'); video.setAttribute('autoplay', true); video.setAttribute('playsinline', true); video.setAttribute('muted', true); return video; } }, { key: '_createStateMachine', value: function _createStateMachine() { var _this5 = this; return StateMachine.create({ initial: 'stopped', events: [{ name: 'start', from: 'stopped', to: 'started' }, { name: 'stop', from: ['started', 'active', 'inactive'], to: 'stopped' }, { name: 'activate', from: ['started', 'inactive'], to: ['active', 'inactive'], condition: function condition(options) { if (Visibility.state() === 'visible' || this.backgroundScan) { return 'active'; } else { return 'inactive'; } } }, { name: 'deactivate', from: ['started', 'active'], to: 'inactive' }], callbacks: { onenteractive: function () { var _ref5 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee4(options) { return _regenerator2.default.wrap(function _callee4$(_context4) { while (1) { switch (_context4.prev = _context4.next) { case 0: _context4.next = 2; return _this5._enableScan(options.args[0]); case 2: _this5.emit('active'); case 3: case 'end': return _context4.stop(); } } }, _callee4, _this5); })); function onenteractive(_x3) { return _ref5.apply(this, arguments); } return onenteractive; }(), onleaveactive: function onleaveactive() { _this5._disableScan(); _this5.emit('inactive'); }, onenteredstarted: function () { var _ref6 = (0, _asyncToGenerator3.default)( /*#__PURE__*/_regenerator2.default.mark(function _callee5(options) { return _regenerator2.default.wrap(function _callee5$(_context5) { while (1) { switch (_context5.prev = _context5.next) { case 0: _context5.next = 2; return _this5._fsm.activate(options.args[0]); case 2: case 'end': return _context5.stop(); } } }, _callee5, _this5); })); function onenteredstarted(_x4) { return _ref6.apply(this, arguments); } return onenteredstarted; }() } }); } }, { key: 'captureImage', set: function set(capture) { this._scanner.captureImage = capture; }, get: function get() { return this._scanner.captureImage; } }, { key: 'scanPeriod', set: function set(period) { this._scanner.scanPeriod = period; }, get: function get() { return this._scanner.scanPeriod; } }, { key: 'refractoryPeriod', set: function set(period) { this._scanner.refractoryPeriod = period; }, get: function get() { return this._scanner.refractoryPeriod; } }, { key: 'continuous', set: function set(continuous) { this._continuous = continuous; if (continuous && this._fsm.current === 'active') { this._scanner.start(); } else { this._scanner.stop(); } }, get: function get() { return this._continuous; } }, { key: 'mirror', set: function set(mirror) { this._mirror = mirror; if (mirror) { this.video.style.MozTransform = 'scaleX(-1)'; this.video.style.webkitTransform = 'scaleX(-1)'; this.video.style.OTransform = 'scaleX(-1)'; this.video.style.msFilter = 'FlipH'; this.video.style.filter = 'FlipH'; this.video.style.transform = 'scaleX(-1)'; } else { this.video.style.MozTransform = null; this.video.style.webkitTransform = null; this.video.style.OTransform = null; this.video.style.msFilter = null; this.video.style.filter = null; this.video.style.transform = null; } }, get: function get() { return this._mirror; } }]); return Scanner; }(EventEmitter); module.exports = Scanner;