1// Copyright 2016-present 650 Industries. All rights reserved.
2
3#import <EXBarCodeScanner/EXBarCodeScanner.h>
4#import <EXBarCodeScanner/EXBarCodeScannerUtils.h>
5#import <UMBarCodeScannerInterface/UMBarCodeScannerInterface.h>
6#import <UMCore/UMDefines.h>
7
8@interface EXBarCodeScanner() <AVCaptureMetadataOutputObjectsDelegate>
9
10@property (nonatomic, strong) AVCaptureMetadataOutput *metadataOutput;
11@property (nonatomic, weak) AVCaptureSession *session;
12@property (nonatomic, weak) dispatch_queue_t sessionQueue;
13@property (nonatomic, copy, nullable) void (^onBarCodeScanned)(NSDictionary*);
14@property (nonatomic, assign, getter=isScanningBarCodes) BOOL barCodesScanning;
15@property (nonatomic, strong) NSDictionary<NSString *, id> *settings;
16
17@end
18
19NSString *const EX_BARCODE_TYPES_KEY = @"barCodeTypes";
20
21@implementation EXBarCodeScanner
22
23- (instancetype)init
24{
25  if (self = [super init]) {
26    _settings = [[NSMutableDictionary alloc] initWithDictionary:[[self class] _getDefaultSettings]];
27  }
28  return self;
29}
30
31# pragma mark - JS properties setters
32
33- (void)setSettings:(NSDictionary<NSString *, id> *)settings
34{
35  for (NSString *key in settings) {
36    if ([key isEqualToString:EX_BARCODE_TYPES_KEY]) {
37      NSArray<NSString *> *value = settings[key];
38      NSSet *previousTypes = [NSSet setWithArray:_settings[EX_BARCODE_TYPES_KEY]];
39      NSSet *newTypes = [NSSet setWithArray:value];
40      if (![previousTypes isEqualToSet:newTypes]) {
41        NSMutableDictionary<NSString *, id> *nextSettings = [[NSMutableDictionary alloc] initWithDictionary:_settings];
42        nextSettings[EX_BARCODE_TYPES_KEY] = value;
43        _settings = nextSettings;
44        UM_WEAKIFY(self);
45        [self _runBlockIfQueueIsPresent:^{
46          UM_ENSURE_STRONGIFY(self);
47          [self maybeStartBarCodeScanning];
48        }];
49      }
50    }
51  }
52}
53
54- (void)setIsEnabled:(BOOL)newBarCodeScanning
55{
56  if ([self isScanningBarCodes] == newBarCodeScanning) {
57    return;
58  }
59  _barCodesScanning = newBarCodeScanning;
60  UM_WEAKIFY(self);
61  [self _runBlockIfQueueIsPresent:^{
62    UM_ENSURE_STRONGIFY(self);
63    if ([self isScanningBarCodes]) {
64      if (self.metadataOutput) {
65        [self _setConnectionsEnabled:true];
66      } else {
67        [self maybeStartBarCodeScanning];
68      }
69    } else {
70      [self _setConnectionsEnabled:false];
71    }
72  }];
73}
74
75# pragma mark - Public API
76
77- (void)maybeStartBarCodeScanning
78{
79  if (!_session || !_sessionQueue || ![self isScanningBarCodes]) {
80    return;
81  }
82
83  if (!_metadataOutput) {
84    [_session beginConfiguration];
85
86    AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
87    [metadataOutput setMetadataObjectsDelegate:self queue:_sessionQueue];
88    if ([_session canAddOutput:metadataOutput]) {
89      [_session addOutput:metadataOutput];
90      _metadataOutput = metadataOutput;
91    }
92    [_session commitConfiguration];
93
94    if (!_metadataOutput) {
95      return;
96    }
97  }
98
99  NSArray<AVMetadataObjectType> *availableRequestedObjectTypes = @[];
100  NSArray<AVMetadataObjectType> *requestedObjectTypes = @[];
101  NSArray<AVMetadataObjectType> *availableObjectTypes = _metadataOutput.availableMetadataObjectTypes;
102  if (_settings && _settings[EX_BARCODE_TYPES_KEY]) {
103    requestedObjectTypes = [[NSArray alloc] initWithArray:_settings[EX_BARCODE_TYPES_KEY]];
104  }
105
106  for(AVMetadataObjectType objectType in requestedObjectTypes) {
107    if ([availableObjectTypes containsObject:objectType]) {
108      availableRequestedObjectTypes = [availableRequestedObjectTypes arrayByAddingObject:objectType];
109    }
110  }
111
112  [_metadataOutput setMetadataObjectTypes:availableRequestedObjectTypes];
113}
114
115- (void)stopBarCodeScanning
116{
117  if (!_session) {
118    return;
119  }
120
121  [_session beginConfiguration];
122
123  if ([_session.outputs containsObject:_metadataOutput]) {
124    [_session removeOutput:_metadataOutput];
125    _metadataOutput = nil;
126  }
127
128  [_session commitConfiguration];
129
130  if ([self isScanningBarCodes] && _onBarCodeScanned) {
131    _onBarCodeScanned(nil);
132  }
133}
134
135# pragma mark - Private API
136
137- (void)_setConnectionsEnabled:(BOOL)enabled
138{
139  if (!_metadataOutput) {
140    return;
141  }
142  for (AVCaptureConnection *connection in _metadataOutput.connections) {
143    connection.enabled = enabled;
144  }
145}
146
147- (void)_runBlockIfQueueIsPresent:(void (^)(void))block
148{
149  if (_sessionQueue) {
150    dispatch_async(_sessionQueue, block);
151  }
152}
153
154# pragma mark - AVCaptureMetadataOutputObjectsDelegate
155
156- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
157       fromConnection:(AVCaptureConnection *)connection
158{
159  if (!_settings || !_settings[EX_BARCODE_TYPES_KEY] || !_metadataOutput) {
160    return;
161  }
162
163  for(AVMetadataObject *metadata in metadataObjects) {
164    if([metadata isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) {
165      AVMetadataMachineReadableCodeObject *codeMetadata = (AVMetadataMachineReadableCodeObject *) metadata;
166      for (id barcodeType in _settings[EX_BARCODE_TYPES_KEY]) {
167        if ([metadata.type isEqualToString:barcodeType]) {
168
169          NSDictionary *event = @{
170                                  @"type" : codeMetadata.type,
171                                  @"data" : codeMetadata.stringValue
172                                  };
173
174          if (_onBarCodeScanned) {
175            _onBarCodeScanned(event);
176          }
177          return;
178        }
179      }
180    }
181  }
182}
183
184# pragma mark - default settings
185
186+ (NSDictionary *)_getDefaultSettings
187{
188  return @{
189           EX_BARCODE_TYPES_KEY: [[EXBarCodeScannerUtils validBarCodeTypes] allValues],
190           };
191}
192
193@end
194