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 <UMPermissionsInterface/UMPermissionsInterface.h> 7#import <UMCore/UMAppLifecycleService.h> 8#import <UMCore/UMUtilities.h> 9 10@interface EXBarCodeScannerView () 11 12@property (nonatomic, strong) dispatch_queue_t sessionQueue; 13@property (nonatomic, strong) AVCaptureSession *session; 14@property (nonatomic, strong) AVCaptureDeviceInput *videoCaptureDeviceInput; 15@property (nonatomic, strong) AVCaptureMetadataOutput *metadataOutput; 16@property (nonatomic, strong) AVCaptureVideoPreviewLayer *previewLayer; 17@property (nonatomic, strong) id runtimeErrorHandlingObserver; 18@property (nonatomic, strong) EXBarCodeScanner *barCodeScanner; 19 20@property (nonatomic, weak) UMModuleRegistry *moduleRegistry; 21@property (nonatomic, weak) id<UMPermissionsInterface> permissionsManager; 22@property (nonatomic, weak) id<UMAppLifecycleService> lifecycleManager; 23 24@property (nonatomic, assign, getter=isSessionPaused) BOOL paused; 25 26@property (nonatomic, copy) UMDirectEventBlock onCameraReady; 27@property (nonatomic, copy) UMDirectEventBlock onMountError; 28@property (nonatomic, copy) UMDirectEventBlock onBarCodeScanned; 29 30@end 31 32@implementation EXBarCodeScannerView 33 34- (instancetype)initWithModuleRegistry:(UMModuleRegistry *)moduleRegistry 35{ 36 if ((self = [super init])) { 37 _presetCamera = AVCaptureDevicePositionBack; 38 _moduleRegistry = moduleRegistry; 39 _session = [AVCaptureSession new]; 40 _sessionQueue = dispatch_queue_create("barCodeScannerQueue", DISPATCH_QUEUE_SERIAL); 41 _lifecycleManager = [moduleRegistry getModuleImplementingProtocol:@protocol(UMAppLifecycleListener)]; 42 _permissionsManager = [moduleRegistry getModuleImplementingProtocol:@protocol(UMPermissionsInterface)]; 43 _barCodeScanner = [self createBarCodeScanner]; 44 45#if !(TARGET_IPHONE_SIMULATOR) 46 _previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session]; 47 _previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill; 48 _previewLayer.needsDisplayOnBoundsChange = YES; 49#endif 50 _paused = NO; 51 52 [_lifecycleManager registerAppLifecycleListener:self]; 53 [[NSNotificationCenter defaultCenter] addObserver:self 54 selector:@selector(orientationChanged:) 55 name:UIDeviceOrientationDidChangeNotification 56 object:nil]; 57 58 [self changePreviewOrientation:[UIApplication sharedApplication].statusBarOrientation]; 59 [self initializeSession]; 60 } 61 return self; 62} 63 64# pragma mark - events 65 66- (void)onReady 67{ 68 if (_onCameraReady) { 69 _onCameraReady(nil); 70 } 71} 72 73- (void)onMountingError:(NSDictionary *)event 74{ 75 if (_onMountError) { 76 _onMountError(event); 77 } 78} 79 80- (void)onBarCodeScanned:(NSDictionary *)event 81{ 82 if (_onBarCodeScanned) { 83 _onBarCodeScanned(event); 84 } 85} 86 87# pragma mark - JS properties setters 88 89- (void)setPresetCamera:(NSInteger)presetCamera 90{ 91 if (_presetCamera == presetCamera) { 92 return; 93 } 94 _presetCamera = presetCamera; 95 UM_WEAKIFY(self); 96 dispatch_async(_sessionQueue, ^{ 97 UM_ENSURE_STRONGIFY(self); 98 [self initializeSession]; 99 }); 100} 101 102- (void)setBarCodeTypes:(NSArray *)barCodeTypes 103{ 104 _barCodeTypes = barCodeTypes; 105 [_barCodeScanner setSettings:@{ 106 @"barCodeTypes": barCodeTypes, 107 }]; 108} 109 110# pragma mark - lifecycle 111 112- (void)layoutSubviews 113{ 114 [super layoutSubviews]; 115 _previewLayer.frame = self.bounds; 116 [self setBackgroundColor:[UIColor blackColor]]; 117 [self.layer insertSublayer:_previewLayer atIndex:0]; 118} 119 120- (void)removeFromSuperview 121{ 122 [_lifecycleManager unregisterAppLifecycleListener:self]; 123 [self stopSession]; 124 [super removeFromSuperview]; 125 [[NSNotificationCenter defaultCenter] removeObserver:self 126 name:UIDeviceOrientationDidChangeNotification 127 object:nil]; 128} 129 130- (void)onAppForegrounded 131{ 132 if (![_session isRunning] && [self isSessionPaused]) { 133 _paused = NO; 134 UM_WEAKIFY(self); 135 dispatch_async(_sessionQueue, ^{ 136 UM_ENSURE_STRONGIFY(self); 137 [self.session startRunning]; 138 }); 139 } 140} 141 142- (void)onAppBackgrounded 143{ 144 if ([_session isRunning] && ![self isSessionPaused]) { 145 _paused = YES; 146 UM_WEAKIFY(self); 147 dispatch_async(_sessionQueue, ^{ 148 UM_ENSURE_STRONGIFY(self); 149 [self.session stopRunning]; 150 }); 151 } 152} 153 154# pragma mark - orientation 155 156- (void)orientationChanged:(NSNotification *)notification 157{ 158 UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation]; 159 [self changePreviewOrientation:orientation]; 160} 161 162- (void)changePreviewOrientation:(UIInterfaceOrientation)orientation 163{ 164 UM_WEAKIFY(self); 165 AVCaptureVideoOrientation videoOrientation = [EXBarCodeScannerUtils videoOrientationForInterfaceOrientation:orientation]; 166 [UMUtilities performSynchronouslyOnMainThread:^{ 167 UM_ENSURE_STRONGIFY(self); 168 if (self.previewLayer.connection.isVideoOrientationSupported) { 169 [self.previewLayer.connection setVideoOrientation:videoOrientation]; 170 } 171 }]; 172} 173 174# pragma mark - session 175 176- (BOOL)ensurePermissionsGranted 177{ 178 NSDictionary *cameraPermissions = [_permissionsManager getPermissionsForResource:@"camera"]; 179 if (![cameraPermissions[@"status"] isEqualToString:@"granted"]) { 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