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