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