1// Copyright 2018-present 650 Industries. All rights reserved.
2
3#import <EXBarCodeScanner/EXBarCodeScannerView.h>
4#import <EXBarCodeScanner/EXBarCodeScanner.h>
5#import <EXBarCodeScanner/EXBarCodeScannerUtils.h>
6#import <EXBarCodeScanner/EXBarCodeCameraRequester.h>
7#import <UMPermissionsInterface/UMPermissionsInterface.h>
8#import <UMCore/UMAppLifecycleService.h>
9#import <UMCore/UMUtilities.h>
10
11@interface EXBarCodeScannerView ()
12
13@property (nonatomic, strong) dispatch_queue_t sessionQueue;
14@property (nonatomic, strong) AVCaptureSession *session;
15@property (nonatomic, strong) AVCaptureDeviceInput *videoCaptureDeviceInput;
16@property (nonatomic, strong) AVCaptureMetadataOutput *metadataOutput;
17@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer;
18@property (nonatomic, strong) id runtimeErrorHandlingObserver;
19@property (nonatomic, strong) EXBarCodeScanner *barCodeScanner;
20
21@property (nonatomic, weak) UMModuleRegistry *moduleRegistry;
22@property (nonatomic, weak) id<UMPermissionsInterface> permissionsManager;
23@property (nonatomic, weak) id<UMAppLifecycleService> lifecycleManager;
24
25@property (nonatomic, assign, getter=isSessionPaused) BOOL paused;
26
27@property (nonatomic, copy) UMDirectEventBlock onCameraReady;
28@property (nonatomic, copy) UMDirectEventBlock onMountError;
29@property (nonatomic, copy) UMDirectEventBlock onBarCodeScanned;
30
31@end
32
33@implementation EXBarCodeScannerView
34
35- (instancetype)initWithModuleRegistry:(UMModuleRegistry *)moduleRegistry
36{
37  if ((self = [super init])) {
38    _presetCamera = AVCaptureDevicePositionBack;
39    _moduleRegistry = moduleRegistry;
40    _session = [AVCaptureSession new];
41    _sessionQueue = dispatch_queue_create("barCodeScannerQueue", DISPATCH_QUEUE_SERIAL);
42    _lifecycleManager = [moduleRegistry getModuleImplementingProtocol:@protocol(UMAppLifecycleListener)];
43    _permissionsManager = [moduleRegistry getModuleImplementingProtocol:@protocol(UMPermissionsInterface)];
44    _barCodeScanner = [self createBarCodeScanner];
45
46#if !(TARGET_IPHONE_SIMULATOR)
47    _previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
48    _previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
49    _previewLayer.needsDisplayOnBoundsChange = YES;
50#endif
51    _paused = NO;
52
53    [_lifecycleManager registerAppLifecycleListener:self];
54    [[NSNotificationCenter defaultCenter] addObserver:self
55                                             selector:@selector(orientationChanged:)
56                                                 name:UIDeviceOrientationDidChangeNotification
57                                               object:nil];
58
59    [self changePreviewOrientation:[UIApplication sharedApplication].statusBarOrientation];
60    [self initializeSession];
61  }
62  return self;
63}
64
65# pragma mark - events
66
67- (void)onReady
68{
69  if (_onCameraReady) {
70    _onCameraReady(nil);
71  }
72}
73
74- (void)onMountingError:(NSDictionary *)event
75{
76  if (_onMountError) {
77    _onMountError(event);
78  }
79}
80
81- (void)onBarCodeScanned:(NSDictionary *)event
82{
83  if (_onBarCodeScanned) {
84    _onBarCodeScanned(event);
85  }
86}
87
88# pragma mark - JS properties setters
89
90- (void)setPresetCamera:(NSInteger)presetCamera
91{
92  if (_presetCamera == presetCamera) {
93    return;
94  }
95  _presetCamera = presetCamera;
96  UM_WEAKIFY(self);
97  dispatch_async(_sessionQueue, ^{
98    UM_ENSURE_STRONGIFY(self);
99    [self initializeSession];
100  });
101}
102
103- (void)setBarCodeTypes:(NSArray *)barCodeTypes
104{
105  _barCodeTypes = barCodeTypes;
106  [_barCodeScanner setSettings:@{
107                                 @"barCodeTypes": barCodeTypes,
108                                 }];
109}
110
111# pragma mark - lifecycle
112
113- (void)layoutSubviews
114{
115  [super layoutSubviews];
116  _previewLayer.frame = self.bounds;
117  [self setBackgroundColor:[UIColor blackColor]];
118  [self.layer insertSublayer:_previewLayer atIndex:0];
119}
120
121- (void)removeFromSuperview
122{
123  [_lifecycleManager unregisterAppLifecycleListener:self];
124  [self stopSession];
125  [super removeFromSuperview];
126  [[NSNotificationCenter defaultCenter] removeObserver:self
127                                                  name:UIDeviceOrientationDidChangeNotification
128                                                object:nil];
129}
130
131- (void)onAppForegrounded
132{
133  if (![_session isRunning] && [self isSessionPaused]) {
134    _paused = NO;
135    UM_WEAKIFY(self);
136    dispatch_async(_sessionQueue, ^{
137      UM_ENSURE_STRONGIFY(self);
138      [self.session startRunning];
139    });
140  }
141}
142
143- (void)onAppBackgrounded
144{
145  if ([_session isRunning] && ![self isSessionPaused]) {
146    _paused = YES;
147    UM_WEAKIFY(self);
148    dispatch_async(_sessionQueue, ^{
149      UM_ENSURE_STRONGIFY(self);
150      [self.session stopRunning];
151    });
152  }
153}
154
155# pragma mark - orientation
156
157- (void)orientationChanged:(NSNotification *)notification
158{
159  UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
160  [self changePreviewOrientation:orientation];
161}
162
163- (void)changePreviewOrientation:(UIInterfaceOrientation)orientation
164{
165  UM_WEAKIFY(self);
166  AVCaptureVideoOrientation videoOrientation = [EXBarCodeScannerUtils videoOrientationForInterfaceOrientation:orientation];
167  [UMUtilities performSynchronouslyOnMainThread:^{
168    UM_ENSURE_STRONGIFY(self);
169    if (self.previewLayer.connection.isVideoOrientationSupported) {
170      [self.previewLayer.connection setVideoOrientation:videoOrientation];
171    }
172  }];
173}
174
175# pragma mark - session
176
177- (BOOL)ensurePermissionsGranted
178{
179  if (![_permissionsManager hasGrantedPermissionUsingRequesterClass:[EXBareCodeCameraRequester class]]) {
180    [self onMountingError:@{@"message": @"Camera permissions not granted - component could not be rendered."}];
181    return FALSE;
182  }
183  return TRUE;
184}
185
186- (void)initializeSession
187{
188  if (_videoCaptureDeviceInput.device.position == _presetCamera) {
189    return;
190  }
191
192  __block UIInterfaceOrientation interfaceOrientation;
193  [UMUtilities performSynchronouslyOnMainThread:^{
194    interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
195  }];
196  AVCaptureVideoOrientation orientation = [EXBarCodeScannerUtils videoOrientationForInterfaceOrientation:interfaceOrientation];
197
198  UM_WEAKIFY(self);
199  dispatch_async(_sessionQueue, ^{
200    UM_ENSURE_STRONGIFY(self);
201
202    [self.session beginConfiguration];
203
204    NSError *error = nil;
205    AVCaptureDevice *captureDevice = [EXBarCodeScannerUtils deviceWithMediaType:AVMediaTypeVideo
206                                                             preferringPosition:self.presetCamera];
207    AVCaptureDeviceInput *captureDeviceInput = [AVCaptureDeviceInput deviceInputWithDevice:captureDevice error:&error];
208
209    if (error || captureDeviceInput == nil) {
210      NSString *errorMessage = @"Camera could not be started - ";
211      if (error) {
212        errorMessage = [errorMessage stringByAppendingString:[error description]];
213      } else {
214        errorMessage = [errorMessage stringByAppendingString:@"there's no captureDeviceInput available"];
215      }
216      [self onMountingError:@{@"message": errorMessage}];
217      return;
218    }
219
220    [self.session removeInput:self.videoCaptureDeviceInput];
221    if ([self.session canAddInput:captureDeviceInput]) {
222      [self.session addInput:captureDeviceInput];
223
224      self.videoCaptureDeviceInput = captureDeviceInput;
225      [self.previewLayer.connection setVideoOrientation:orientation];
226    }
227
228    [self.session commitConfiguration];
229    if (!self.session.isRunning) {
230      [self startSession];
231    }
232  });
233}
234
235- (void)startSession
236{
237#pragma clang diagnostic push
238#pragma clang diagnostic ignored "-Wunreachable-code"
239#if TARGET_IPHONE_SIMULATOR
240  return;
241#endif
242  if (![self ensurePermissionsGranted]) {
243    return;
244  };
245
246  UM_WEAKIFY(self);
247  dispatch_async(_sessionQueue, ^{
248    UM_ENSURE_STRONGIFY(self);
249
250    if (self.presetCamera == AVCaptureDevicePositionUnspecified) {
251      return;
252    }
253
254    [self setRuntimeErrorHandlingObserver:
255     [[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureSessionRuntimeErrorNotification
256                                                       object:self.session
257                                                        queue:nil
258                                                   usingBlock:^(NSNotification *note) {
259      UM_ENSURE_STRONGIFY(self);
260      dispatch_async(self.sessionQueue, ^{
261        UM_ENSURE_STRONGIFY(self);
262        // Manually restarting the session since it must have been stopped due to an error.
263        [self.session startRunning];
264        [self onReady];
265      });
266    }]];
267
268    [self.barCodeScanner maybeStartBarCodeScanning];
269
270    [self.session startRunning];
271    [self onReady];
272  });
273#pragma clang diagnostic pop
274}
275
276- (void)stopSession
277{
278#if TARGET_IPHONE_SIMULATOR
279  return;
280#endif
281  UM_WEAKIFY(self);
282  dispatch_async(_sessionQueue, ^{
283    UM_ENSURE_STRONGIFY(self);
284
285    [self.barCodeScanner stopBarCodeScanning];
286
287    [self.previewLayer removeFromSuperlayer];
288    [self.session commitConfiguration];
289    [self.session stopRunning];
290    for (AVCaptureInput *input in self.session.inputs) {
291      [self.session removeInput:input];
292    }
293
294    for (AVCaptureOutput *output in self.session.outputs) {
295      [self.session removeOutput:output];
296    }
297  });
298}
299
300# pragma mark - BarCode scanner
301
302- (EXBarCodeScanner *)createBarCodeScanner
303{
304  EXBarCodeScanner *barCodeScanner = [EXBarCodeScanner new];
305  [barCodeScanner setSession:_session];
306  [barCodeScanner setSessionQueue:_sessionQueue];
307  UM_WEAKIFY(self);
308  [barCodeScanner setOnBarCodeScanned:^(NSDictionary *body) {
309    UM_ENSURE_STRONGIFY(self);
310    [self onBarCodeScanned:body];
311  }];
312  [barCodeScanner setIsEnabled:true];
313  return barCodeScanner;
314}
315
316@end
317