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