@nstudio/nativescript-barcodescanner
Version:
Scan QR/barcodes with your NativeScript app.
457 lines • 20.4 kB
JavaScript
import { Frame } from '@nativescript/core';
import { BarcodeScannerView as BarcodeScannerBaseView } from './common';
export class BarcodeScannerView extends BarcodeScannerBaseView {
constructor() {
super();
this._hasSupport = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo) !== null;
if (this._hasSupport) {
if (typeof AVAudioSession.sharedInstance().setCategoryModeOptionsError === 'function') {
// if music was playing, it would stop unless we do this:
AVAudioSession.sharedInstance().setCategoryModeOptionsError(AVAudioSessionCategoryPlayback, AVAudioSessionModeDefault, 1 /* MixWithOthers */);
}
}
}
createNativeView() {
let v = super.createNativeView();
if (this._hasSupport) {
this.initView();
}
return v;
}
initView() {
const types = getBarcodeTypes(this.formats);
this._reader = QRCodeReader.readerWithMetadataObjectTypes(types);
let torch = false;
let flip = false;
let closeButtonLabel = null;
let cancelLabelBackgroundColor = null;
if (this.preferFrontCamera) {
this._reader.switchDeviceInput();
}
this._scanner = QRCodeReaderViewController.readerWithCancelButtonTitleCodeReaderStartScanningAtLoadShowSwitchCameraButtonShowTorchButtonCancelButtonBackgroundColor(closeButtonLabel, this._reader, true, flip, torch, cancelLabelBackgroundColor);
this._scanner.modalPresentationStyle = 3 /* CurrentContext */;
const that = this;
this._delegate = QRCodeReaderDelegateImpl.initWithOwner(new WeakRef(this));
this._delegate.setCallback(this.beepOnScan, true, this.reportDuplicates, this.formats, (text, format) => {
that.notify({
eventName: BarcodeScannerBaseView.scanResultEvent,
object: that,
format: format,
text: text,
});
});
this._scanner.delegate = this._delegate;
setTimeout(() => {
if (this.ios && this.ios.layer) {
this.ios.layer.insertSublayerAtIndex(this._reader.previewLayer, 0);
if (!this.pause) {
this._reader.startScanning();
}
}
}, 0);
}
onLayout(left, top, right, bottom) {
super.onLayout(left, top, right, bottom);
if (this._hasSupport && this.ios && this._reader) {
this._reader.previewLayer.frame = this.ios.layer.bounds;
}
}
pauseScanning() {
if (this._reader && this._reader.running()) {
this._reader.stopScanning();
}
}
resumeScanning() {
if (this._reader && !this._reader.running()) {
this._reader.startScanning();
}
}
}
export class BarcodeScanner {
constructor() {
if (typeof AVAudioSession.sharedInstance().setCategoryModeOptionsError === 'function') {
// if music was playing, it would stop unless we do this:
AVAudioSession.sharedInstance().setCategoryModeOptionsError(AVAudioSessionCategoryPlayback, AVAudioSessionModeDefault, 1 /* MixWithOthers */);
}
this._device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo);
if (this._device && this._device.hasTorch && this._device.hasFlash) {
this._observer = VolumeObserverClass.alloc().init();
this._observer['_owner'] = this;
}
}
_hasCameraPermission() {
let authStatus = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo);
return authStatus === 3 /* Authorized */;
}
_hasDeniedCameraPermission() {
let authStatus = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo);
return authStatus === 2 /* Denied */ || authStatus === 1 /* Restricted */;
}
_addVolumeObserver() {
if (!this._observer) {
return;
}
this._audioSession = AVAudioSession.sharedInstance();
this._audioSession.setActiveError(true);
this._currentVolume = this._audioSession.outputVolume;
if (!this._observerActive) {
this._audioSession.addObserverForKeyPathOptionsContext(this._observer, 'outputVolume', 0, null);
this._observerActive = true;
}
}
_removeVolumeObserver() {
try {
if (this._observerActive) {
this._observerActive = false;
this._audioSession.removeObserverForKeyPath(this._observer, 'outputVolume');
}
}
catch (ignore) { }
}
_enableTorch() {
this._device.lockForConfiguration();
this._device.setTorchModeOnWithLevelError(AVCaptureMaxAvailableTorchLevel);
this._device.flashMode = 1 /* On */;
this._device.unlockForConfiguration();
}
_disableTorch() {
this._device.lockForConfiguration();
this._device.torchMode = 0 /* Off */;
this._device.flashMode = 0 /* Off */;
this._device.unlockForConfiguration();
}
available() {
return new Promise((resolve, reject) => {
resolve(!!this._device);
});
}
hasCameraPermission() {
return new Promise((resolve) => {
resolve(this._hasCameraPermission());
});
}
requestCameraPermission() {
return new Promise((resolve, reject) => {
const cameraStatus = AVCaptureDevice.authorizationStatusForMediaType(AVMediaTypeVideo);
switch (cameraStatus) {
case 0 /* NotDetermined */: {
AVCaptureDevice.requestAccessForMediaTypeCompletionHandler(AVMediaTypeVideo, (granted) => (granted ? resolve() : reject()));
break;
}
case 3 /* Authorized */: {
resolve();
break;
}
case 1 /* Restricted */:
case 2 /* Denied */: {
reject();
break;
}
}
});
}
stop() {
return new Promise((resolve, reject) => {
try {
this.close();
this._removeVolumeObserver();
if (this._scanner) {
this._scanner.stopScanning();
}
this._closeCallback && this._closeCallback();
resolve();
}
catch (ex) {
reject(ex);
}
});
}
scan(arg) {
return new Promise((resolve, reject) => {
try {
// only need for denied permission as conveniently, this method will auto-request permission upon scan
if (this._hasDeniedCameraPermission()) {
if (arg.openSettingsIfPermissionWasPreviouslyDenied) {
UIApplication.sharedApplication.openURL(NSURL.URLWithString(UIApplicationOpenSettingsURLString));
}
reject('The user previously denied permission to access the camera.');
return;
}
this._addVolumeObserver();
arg = arg || {};
let closeButtonLabel = arg.cancelLabel || 'Close';
let isContinuous = typeof arg.continuousScanCallback === 'function';
this._closeCallback = arg.closeCallback;
const types = getBarcodeTypes(arg.formats);
const reader = QRCodeReader.readerWithMetadataObjectTypes(types);
if (arg.preferFrontCamera && reader.hasFrontDevice()) {
reader.switchDeviceInput();
}
let torch = arg.showTorchButton;
let flip = arg.showFlipCameraButton;
let startScanningAtLoad = true;
this._scanner = QRCodeReaderViewController.readerWithCancelButtonTitleCodeReaderStartScanningAtLoadShowSwitchCameraButtonShowTorchButtonCancelButtonBackgroundColor(closeButtonLabel, reader, startScanningAtLoad, flip, torch, arg.cancelLabelBackgroundColor);
this._scanner.modalPresentationStyle = arg.fullScreen ? 0 /* FullScreen */ : 2 /* FormSheet */;
this._scanDelegate = QRCodeReaderDelegateImpl.initWithOwner(new WeakRef(this));
this._scanner.delegate = this._scanDelegate;
this._scanDelegate.setCallback(arg.beepOnScan !== false, isContinuous, arg.reportDuplicates, arg.formats, (text, barcodeFormat) => {
// invoke the callback / promise
if (text === undefined) {
this._removeVolumeObserver();
this._closeCallback && this._closeCallback();
reject('Scan aborted');
}
else {
let value = text;
if (shouldReturnEAN13AsUPCA(barcodeFormat, value, arg.formats)) {
barcodeFormat = 'UPC_A';
value = value.substring(1);
}
const result = {
format: barcodeFormat,
text: value,
};
if (isContinuous) {
arg.continuousScanCallback(result);
}
else {
this._removeVolumeObserver();
this._closeCallback && this._closeCallback();
resolve(result);
}
}
});
if (this._device && this._device.autoFocusRangeRestrictionSupported) {
this._device.lockForConfiguration();
this._device.autoFocusRangeRestriction = 1 /* Near */;
if (this._device.smoothAutoFocusSupported) {
this._device.smoothAutoFocusEnabled = true;
}
this._device.unlockForConfiguration();
}
setTimeout(() => {
this.getViewControllerToPresentFrom(arg.presentInRootViewController).presentViewControllerAnimatedCompletion(this._scanner, true, () => {
if (arg.torchOn) {
this._enableTorch();
}
});
}, this.isPresentingModally() ? 650 : 0);
}
catch (ex) {
console.log('Error in barcodescanner.scan: ' + ex);
reject(ex);
}
});
}
isPresentingModally() {
let viewController;
const topMostFrame = Frame.topmost();
if (topMostFrame) {
viewController = topMostFrame.currentPage && topMostFrame.currentPage.ios;
if (viewController) {
while (viewController.parentViewController) {
viewController = viewController.parentViewController;
}
return !!viewController.presentedViewController;
}
}
return false;
}
close() {
if (this._lastScanViewController) {
this._lastScanViewController.dismissViewControllerAnimatedCompletion(true, null);
this._lastScanViewController = undefined;
}
else {
this.getViewControllerToPresentFrom().dismissViewControllerAnimatedCompletion(true, null);
}
}
getViewControllerToPresentFrom(presentInRootViewController) {
let viewController;
const topMostFrame = Frame.topmost();
if (topMostFrame && presentInRootViewController !== true) {
viewController = topMostFrame.currentPage && topMostFrame.currentPage.ios;
if (viewController) {
while (viewController.parentViewController) {
// find top-most view controler
viewController = viewController.parentViewController;
}
while (viewController.presentedViewController) {
// find last presented modal
viewController = viewController.presentedViewController;
}
}
}
if (!viewController) {
viewController = UIApplication.sharedApplication.keyWindow.rootViewController;
}
this._lastScanViewController = viewController;
return viewController;
}
}
const shouldReturnEAN13AsUPCA = (barcodeFormat, value, requestedFormats) => {
return barcodeFormat === 'EAN_13' && value.indexOf('0') === 0;
// why not add the line below? Well, see https://github.com/EddyVerbruggen/nativescript-barcodescanner/issues/176
// && (!requestedFormats || requestedFormats.indexOf("UPC_A") > -1);
};
const getBarcodeFormat = (nativeFormat) => {
if (nativeFormat === AVMetadataObjectTypeQRCode)
return 'QR_CODE';
else if (nativeFormat === AVMetadataObjectTypePDF417Code)
return 'PDF_417';
else if (nativeFormat === AVMetadataObjectTypeAztecCode)
return 'AZTEC';
else if (nativeFormat === AVMetadataObjectTypeUPCECode)
return 'UPC_E';
else if (nativeFormat === AVMetadataObjectTypeCode39Code)
return 'CODE_39';
else if (nativeFormat === AVMetadataObjectTypeCode39Mod43Code)
return 'CODE_39_MOD_43';
else if (nativeFormat === AVMetadataObjectTypeCode93Code)
return 'CODE_93';
else if (nativeFormat === AVMetadataObjectTypeCode128Code)
return 'CODE_128';
else if (nativeFormat === AVMetadataObjectTypeDataMatrixCode)
return 'DATA_MATRIX';
else if (nativeFormat === AVMetadataObjectTypeEAN8Code)
return 'EAN_8';
else if (nativeFormat === AVMetadataObjectTypeITF14Code)
return 'ITF';
else if (nativeFormat === AVMetadataObjectTypeEAN13Code)
return 'EAN_13';
else if (nativeFormat === AVMetadataObjectTypeInterleaved2of5Code)
return 'INTERLEAVED_2_OF_5';
else {
console.log('Unknown format scanned: ' + nativeFormat + ', please report this at https://github.com/EddyVerbruggen/nativescript-barcodescanner/issues');
return nativeFormat;
}
};
const getBarcodeTypes = (formatsString) => {
const types = [];
if (formatsString) {
let formats = formatsString.split(',');
for (let format of formats) {
format = format.trim();
if (format === 'QR_CODE')
types.push(AVMetadataObjectTypeQRCode);
else if (format === 'PDF_417')
types.push(AVMetadataObjectTypePDF417Code);
else if (format === 'AZTEC')
types.push(AVMetadataObjectTypeAztecCode);
else if (format === 'UPC_E')
types.push(AVMetadataObjectTypeUPCECode);
else if (format === 'CODE_39')
types.push(AVMetadataObjectTypeCode39Code);
else if (format === 'CODE_39_MOD_43')
types.push(AVMetadataObjectTypeCode39Mod43Code);
else if (format === 'CODE_93')
types.push(AVMetadataObjectTypeCode93Code);
else if (format === 'CODE_128')
types.push(AVMetadataObjectTypeCode128Code);
else if (format === 'DATA_MATRIX')
types.push(AVMetadataObjectTypeDataMatrixCode);
else if (format === 'EAN_8')
types.push(AVMetadataObjectTypeEAN8Code);
else if (format === 'ITF')
types.push(AVMetadataObjectTypeITF14Code);
else if (format === 'INTERLEAVED_2_OF_5')
types.push(AVMetadataObjectTypeInterleaved2of5Code);
// see https://github.com/EddyVerbruggen/nativescript-barcodescanner/issues/176
else if (format === 'EAN_13' || format === 'UPC_A')
types.push(AVMetadataObjectTypeEAN13Code);
}
}
else {
types.push(AVMetadataObjectTypeUPCECode, AVMetadataObjectTypeCode39Code, AVMetadataObjectTypeCode39Mod43Code, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode93Code, AVMetadataObjectTypeCode128Code, AVMetadataObjectTypeDataMatrixCode, AVMetadataObjectTypeITF14Code, AVMetadataObjectTypePDF417Code, AVMetadataObjectTypeQRCode, AVMetadataObjectTypeAztecCode, AVMetadataObjectTypeInterleaved2of5Code);
}
return types;
};
var QRCodeReaderDelegateImpl = /** @class */ (function (_super) {
__extends(QRCodeReaderDelegateImpl, _super);
function QRCodeReaderDelegateImpl() {
var _this = _super !== null && _super.apply(this, arguments) || this;
// initializing this value may prevent recognizing too quickly
_this._lastScanResultTs = new Date().getTime();
return _this;
}
QRCodeReaderDelegateImpl.initWithOwner = function (owner) {
var delegate = QRCodeReaderDelegateImpl.new();
delegate._owner = owner;
return delegate;
};
QRCodeReaderDelegateImpl.prototype.setCallback = function (beepOnScan, isContinuous, reportDuplicates, requestedFormats, callback) {
this._isContinuous = isContinuous;
this._reportDuplicates = reportDuplicates;
this._requestedFormats = requestedFormats;
this._callback = callback;
this._beepOnScan = beepOnScan;
};
QRCodeReaderDelegateImpl.prototype.readerDidCancel = function (reader) {
this._owner.get().close();
this._callback();
};
QRCodeReaderDelegateImpl.prototype.readerDidScanResultForType = function (reader, result, type) {
var validResult = false;
var barcodeFormat = getBarcodeFormat(type);
var value = result;
if (shouldReturnEAN13AsUPCA(barcodeFormat, value, this._requestedFormats)) {
barcodeFormat = 'UPC_A';
value = value.substring(1);
}
if (this._isContinuous) {
if (!this._scannedArray) {
this._scannedArray = Array();
}
// don't report duplicates unless explicitly requested
var newResult = this._scannedArray.indexOf('[' + value + '][' + barcodeFormat + ']') === -1;
if (newResult || this._reportDuplicates) {
var now = new Date().getTime();
// prevent flooding the callback
if (now - this._lastScanResultTs < 1700) {
return;
}
this._lastScanResultTs = now;
validResult = true;
this._scannedArray.push('[' + value + '][' + barcodeFormat + ']');
this._callback(value, barcodeFormat);
}
}
else {
validResult = true;
this._owner.get().close();
this._callback(value, barcodeFormat);
}
if (validResult && this._beepOnScan) {
// tone
AudioServicesPlaySystemSound(1200);
// weak-boom (taptic feedback)
setTimeout(function () {
AudioServicesPlaySystemSound(1519);
});
}
};
QRCodeReaderDelegateImpl.ObjCProtocols = [QRCodeReaderDelegate];
return QRCodeReaderDelegateImpl;
}(NSObject));
var VolumeObserverClass = /** @class */ (function (_super) {
__extends(VolumeObserverClass, _super);
function VolumeObserverClass() {
return _super !== null && _super.apply(this, arguments) || this;
}
VolumeObserverClass.prototype.observeValueForKeyPathOfObjectChangeContext = function (path, obj, change, context) {
if (path === 'outputVolume') {
var volumeLevel = MPMusicPlayerController.applicationMusicPlayer.volume;
if (volumeLevel > this['_owner']._currentVolume) {
// volume up button pressed, so enable torch
this['_owner']._enableTorch();
}
else {
// volume down button pressed, so disable torch
this['_owner']._disableTorch();
}
this['_owner']._currentVolume = volumeLevel;
}
};
return VolumeObserverClass;
}(NSObject));
//# sourceMappingURL=index.ios.js.map