1// Copyright 2016-present 650 Industries. All rights reserved.
2
3#import <EXBarCodeScanner/EXBarCodeScanner.h>
4#import <EXBarCodeScanner/EXBarCodeScannerUtils.h>
5#import <EXBarCodeScannerInterface/EXBarCodeScannerInterface.h>
6#import <EXCore/EXDefines.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        [self maybeStartBarCodeScanning];
45      }
46    }
47  }
48}
49
50- (void)setIsEnabled:(BOOL)newBarCodeScanning
51{
52  if ([self isScanningBarCodes] == newBarCodeScanning) {
53    return;
54  }
55  _barCodesScanning = newBarCodeScanning;
56  EX_WEAKIFY(self);
57  [self _runBlockIfQueueIsPresent:^{
58    EX_ENSURE_STRONGIFY(self);
59    if ([self isScanningBarCodes]) {
60      if (self.metadataOutput) {
61        [self _setConnectionsEnabled:true];
62      } else {
63        [self maybeStartBarCodeScanning];
64      }
65    } else {
66      [self _setConnectionsEnabled:false];
67    }
68  }];
69}
70
71# pragma mark - Public API
72
73- (void)maybeStartBarCodeScanning
74{
75  if (!_session || !_sessionQueue || ![self isScanningBarCodes]) {
76    return;
77  }
78
79  if (!_metadataOutput) {
80    [_session beginConfiguration];
81
82    AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
83    [metadataOutput setMetadataObjectsDelegate:self queue:_sessionQueue];
84    if ([_session canAddOutput:metadataOutput]) {
85      [_session addOutput:metadataOutput];
86      _metadataOutput = metadataOutput;
87    }
88    [_session commitConfiguration];
89
90    if (!_metadataOutput) {
91      return;
92    }
93  }
94
95  NSArray<AVMetadataObjectType> *availableRequestedObjectTypes = @[];
96  NSArray<AVMetadataObjectType> *requestedObjectTypes = @[];
97  NSArray<AVMetadataObjectType> *availableObjectTypes = _metadataOutput.availableMetadataObjectTypes;
98  if (_settings && _settings[EX_BARCODE_TYPES_KEY]) {
99    requestedObjectTypes = [[NSArray alloc] initWithArray:_settings[EX_BARCODE_TYPES_KEY]];
100  }
101
102  for(AVMetadataObjectType objectType in requestedObjectTypes) {
103    if ([availableObjectTypes containsObject:objectType]) {
104      availableRequestedObjectTypes = [availableRequestedObjectTypes arrayByAddingObject:objectType];
105    }
106  }
107
108  [_metadataOutput setMetadataObjectTypes:availableRequestedObjectTypes];
109}
110
111- (void)stopBarCodeScanning
112{
113  if (!_session) {
114    return;
115  }
116
117  [_session beginConfiguration];
118
119  if ([_session.outputs containsObject:_metadataOutput]) {
120    [_session removeOutput:_metadataOutput];
121    _metadataOutput = nil;
122  }
123
124  [_session commitConfiguration];
125
126  if ([self isScanningBarCodes] && _onBarCodeScanned) {
127    _onBarCodeScanned(nil);
128  }
129}
130
131# pragma mark - Private API
132
133- (void)_setConnectionsEnabled:(BOOL)enabled
134{
135  if (!_metadataOutput) {
136    return;
137  }
138  for (AVCaptureConnection *connection in _metadataOutput.connections) {
139    connection.enabled = enabled;
140  }
141}
142
143- (void)_runBlockIfQueueIsPresent:(void (^)(void))block
144{
145  if (_sessionQueue) {
146    dispatch_async(_sessionQueue, block);
147  }
148}
149
150# pragma mark - AVCaptureMetadataOutputObjectsDelegate
151
152- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects
153       fromConnection:(AVCaptureConnection *)connection
154{
155  if (!_settings || !_settings[EX_BARCODE_TYPES_KEY] || !_metadataOutput) {
156    return;
157  }
158
159  for(AVMetadataObject *metadata in metadataObjects) {
160    if([metadata isKindOfClass:[AVMetadataMachineReadableCodeObject class]]) {
161      AVMetadataMachineReadableCodeObject *codeMetadata = (AVMetadataMachineReadableCodeObject *) metadata;
162      for (id barcodeType in _settings[EX_BARCODE_TYPES_KEY]) {
163        if ([metadata.type isEqualToString:barcodeType]) {
164
165          NSDictionary *event = @{
166                                  @"type" : codeMetadata.type,
167                                  @"data" : codeMetadata.stringValue
168                                  };
169
170          if (_onBarCodeScanned) {
171            _onBarCodeScanned(event);
172          }
173          return;
174        }
175      }
176    }
177  }
178}
179
180# pragma mark - default settings
181
182+ (NSDictionary *)_getDefaultSettings
183{
184  return @{
185           EX_BARCODE_TYPES_KEY: [[EXBarCodeScannerUtils validBarCodeTypes] allValues],
186           };
187}
188
189@end
190