├── .gitignore ├── README.md ├── ZBarQRScannerRectView.js ├── ZBarScannerView.js ├── android ├── BUCK ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── barcodescanner │ ├── MutableImage.java │ ├── RCTCamera.java │ ├── RCTCameraUtils.java │ ├── RCTCameraView.java │ ├── RCTCameraViewFinder.java │ ├── RCTCameraViewManager.java │ ├── RCTSensorOrientationChecker.java │ ├── RCTZBarCameraModule.java │ └── RCTZBarCameraPackage.java ├── package.json └── yarn.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | project.xcworkspace 24 | 25 | # Android/IntelliJ 26 | # 27 | build/ 28 | .idea 29 | .gradle 30 | local.properties 31 | *.iml 32 | 33 | # node.js 34 | # 35 | node_modules/ 36 | npm-debug.log 37 | yarn-error.log 38 | 39 | # BUCK 40 | buck-out/ 41 | \.buckd/ 42 | *.keystore 43 | 44 | # fastlane 45 | # 46 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 47 | # screenshots whenever they are needed. 48 | # For more information about the recommended setup visit: 49 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 50 | 51 | fastlane/report.xml 52 | fastlane/Preview.html 53 | fastlane/screenshots 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## react-native-android-barcodescanner [![npm version](https://badge.fury.io/js/react-native-android-barcodescanner.svg)](https://badge.fury.io/js/react-native-android-barcodescanner) 2 | ## 第一步 3 | 工程目录下运行 npm install --save react-native-android-barcodescanner 或者 yarn add react-native-android-barcodescanner(已经安装了yarn) 4 | 5 | ### 注意: 6 | version 0.1.1适用于大于等于rn0.44和小于0.47
7 | version 0.1.5适用于rn0.47及以上 8 | 加入``````权限 9 | ## 第二步 10 | 运行 react-native link react-native-android-barcodescanner 11 | ## 第三部使用 12 | 在工程中导入: 13 | ```bash 14 | import ZbarScannerView from 'react-native-android-barcodescanner'; 15 | {ToastAndroid.show(JSON.stringify(data),ToastAndroid.SHORT)}} 18 | hintText={''} 19 | renderTopBarView={() => } 20 | renderBottomMenuView={() => } 21 | /> 22 |
23 | ``` 24 | ## Props 25 | 26 | ![](https://github.com/MarnoDev/AC-QRCode-RN/blob/master/screenshots/ac-qrcode-props.jpg) 27 | 28 | |Prop|Type|Default|Optional| 29 | |:--:|:--:|:--:|:--:| 30 | |maskColor|string|#0000004D|true| 31 | |borderColor|string|#000000|true| 32 | |cornerColor|string|#000000|true| 33 | |borderWidth|number|0|true| 34 | |cornerBorderWidth|number|4|true| 35 | |cornerBorderLength|number|20|true| 36 | |rectHeight|number|200|true| 37 | |rectWidth|number|200|true| 38 | |isCornerOffset|bool|false|true| 39 | |cornerOffsetSize|number|0|true| 40 | |bottomMenuHeight|number|0|true| 41 | |scanBarAnimateTime|number|2500|true| 42 | |scanBarColor|string|#22ff00|true| 43 | |scanBarImage|any|null|true| 44 | |scanBarHeight|number|1.5|true| 45 | |scanBarMargin|number|6|true| 46 | |hintText|string|将二维码/条码放入框内,
即可自动扫描|true| 47 | |hintTextStyle|object|{ color: '#fff',
fontSize: 14,
backgroundColor:'transparent'}|true| 48 | |hintTextPosition|number|130|true| 49 | |isShowScanBar|bool|true|true| 50 | |bottomMenuStyle|object|-|true| 51 | |renderTopBarView|func|-|flase| 52 | |renderBottomMenuView|func|-|false| 53 | |onScanResultReceived|func|-|false| 54 | -------------------------------------------------------------------------------- /ZBarQRScannerRectView.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Camera from './ZBarScannerView'; 3 | import 4 | { 5 | ActivityIndicator, 6 | StyleSheet, 7 | View, 8 | Animated, 9 | Easing, 10 | Text, 11 | Image 12 | } from 'react-native'; 13 | 14 | /** 15 | * 扫描界面遮罩 16 | * 单独写一个类,方便拷贝使用 17 | */ 18 | class ZBarQRScannerRectView extends Component { 19 | static defaultProps = { 20 | maskColor: '#0000004D', 21 | cornerColor: '#22ff00', 22 | borderColor: '#000000', 23 | rectHeight: 200, 24 | rectWidth: 200, 25 | borderWidth: 0, 26 | cornerBorderWidth: 4, 27 | cornerBorderLength: 20, 28 | isLoading: false, 29 | cornerOffsetSize: 0, 30 | isCornerOffset: false, 31 | bottomMenuHeight: 0, 32 | scanBarAnimateTime: 2500, 33 | scanBarColor: '#22ff00', 34 | scanBarImage: null, 35 | scanBarHeight: 1.5, 36 | scanBarMargin: 6, 37 | hintText: '将二维码/条码放入框内,即可自动扫描', 38 | hintTextStyle: {color: '#fff', fontSize: 14,backgroundColor:'transparent'}, 39 | hintTextPosition: 130, 40 | isShowScanBar:true 41 | }; 42 | 43 | constructor(props) { 44 | super(props); 45 | 46 | this.getBackgroundColor = this.getBackgroundColor.bind(this); 47 | this.getRectSize = this.getRectSize.bind(this); 48 | this.getCornerSize = this.getCornerSize.bind(this); 49 | this.renderLoadingIndicator = this.renderLoadingIndicator.bind(this); 50 | 51 | this.state = { 52 | topWidth: 0, 53 | topHeight: 0, 54 | leftWidth: 0, 55 | animatedValue: new Animated.Value(0), 56 | } 57 | } 58 | 59 | //获取背景颜色 60 | getBackgroundColor() { 61 | return ({ 62 | backgroundColor: this.props.maskColor, 63 | }); 64 | } 65 | 66 | //获取扫描框背景大小 67 | getRectSize() { 68 | return ({ 69 | height: this.props.rectHeight, 70 | width: this.props.rectWidth, 71 | }); 72 | } 73 | 74 | //获取扫描框边框大小 75 | getBorderSize() { 76 | if (this.props.isCornerOffset) { 77 | return ({ 78 | height: this.props.rectHeight - this.props.cornerOffsetSize * 2, 79 | width: this.props.rectWidth - this.props.cornerOffsetSize * 2, 80 | }); 81 | } else { 82 | return ({ 83 | height: this.props.rectHeight, 84 | width: this.props.rectWidth, 85 | }); 86 | } 87 | } 88 | 89 | //获取扫描框转角的颜色 90 | getCornerColor() { 91 | return ({ 92 | borderColor: this.props.cornerColor, 93 | }); 94 | } 95 | 96 | //获取扫描框转角的大小 97 | getCornerSize() { 98 | return ({ 99 | height: this.props.cornerBorderLength, 100 | width: this.props.cornerBorderLength, 101 | }); 102 | } 103 | 104 | //获取扫描框大小 105 | getBorderWidth() { 106 | return ({ 107 | borderWidth: this.props.borderWidth, 108 | }); 109 | } 110 | 111 | //获取扫描框颜色 112 | getBorderColor() { 113 | return ({ 114 | borderColor: this.props.borderColor, 115 | }); 116 | } 117 | 118 | //渲染加载动画 119 | renderLoadingIndicator() { 120 | if (!this.props.isLoading) { 121 | return null; 122 | } 123 | 124 | return ( 125 | 130 | ); 131 | } 132 | 133 | //测量整个扫描组件的大小 134 | measureTotalSize(e) { 135 | let totalSize = e.layout; 136 | this.setState({ 137 | topWidth: totalSize.width, 138 | }) 139 | } 140 | 141 | //测量扫描框的位置 142 | measureRectPosition(e) { 143 | let rectSize = e.layout; 144 | this.setState({ 145 | topHeight: rectSize.y, 146 | leftWidth: rectSize.x, 147 | }) 148 | } 149 | 150 | //获取顶部遮罩高度 151 | getTopMaskHeight() { 152 | if (this.props.isCornerOffset) { 153 | return this.state.topHeight + this.props.rectHeight - this.props.cornerOffsetSize; 154 | } else { 155 | return this.state.topHeight + this.props.rectHeight; 156 | } 157 | } 158 | 159 | //获取底部遮罩高度 160 | getBottomMaskHeight() { 161 | if (this.props.isCornerOffset) { 162 | return this.props.rectHeight + this.state.topHeight - this.props.cornerOffsetSize; 163 | } else { 164 | return this.state.topHeight + this.props.rectHeight; 165 | } 166 | } 167 | 168 | //获取左右两边遮罩高度 169 | getSideMaskHeight() { 170 | if (this.props.isCornerOffset) { 171 | return this.props.rectHeight - this.props.cornerOffsetSize * 2; 172 | } else { 173 | return this.props.rectHeight; 174 | } 175 | } 176 | 177 | //获取左右两边遮罩宽度 178 | getSideMaskWidth() { 179 | if (this.props.isCornerOffset) { 180 | return this.state.leftWidth + this.props.cornerOffsetSize; 181 | } else { 182 | return this.state.leftWidth; 183 | } 184 | } 185 | 186 | getBottomMenuHeight() { 187 | return ({ 188 | bottom: this.props.bottomMenuHeight, 189 | }); 190 | } 191 | 192 | getScanBarMargin() { 193 | return ({ 194 | marginRight: this.props.scanBarMargin, 195 | marginLeft: this.props.scanBarMargin, 196 | }) 197 | } 198 | 199 | getScanImageWidth() { 200 | return this.props.rectWidth - this.props.scanBarMargin * 2 201 | } 202 | 203 | //绘制扫描线 204 | _renderScanBar() { 205 | if(!this.props.isShowScanBar) return; 206 | if (this.props.scanBarImage) { 207 | return 209 | } else { 210 | return 214 | } 215 | } 216 | 217 | render() { 218 | const animatedStyle = { 219 | transform: [ 220 | {translateY: this.state.animatedValue} 221 | ] 222 | }; 223 | 224 | return ( 225 | this.measureTotalSize(e)} 227 | style={[styles.container, this.getBottomMenuHeight()]}> 228 | 229 | this.measureRectPosition(e)} 231 | > 232 | 233 | {/*扫描框边线*/} 234 | 239 | 240 | 243 | {this._renderScanBar()} 244 | 245 | 246 | 247 | 248 | {/*扫描框转角-左上角*/} 249 | 258 | 259 | {/*扫描框转角-右上角*/} 260 | 269 | 270 | {/*加载动画*/} 271 | {this.renderLoadingIndicator()} 272 | 273 | {/*扫描框转角-左下角*/} 274 | 283 | 284 | {/*扫描框转角-右下角*/} 285 | 294 | 295 | 296 | 304 | 305 | 313 | 314 | 321 | 322 | 329 | 330 | 331 | {this.props.hintText} 332 | 333 | 334 | 335 | ); 336 | } 337 | 338 | componentDidMount() { 339 | this.scannerLineMove(); 340 | } 341 | 342 | scannerLineMove() { 343 | this.state.animatedValue.setValue(0); //重置Rotate动画值为0 344 | Animated.timing(this.state.animatedValue, { 345 | toValue: this.props.rectHeight, 346 | duration: this.props.scanBarAnimateTime, 347 | easing: Easing.linear 348 | }).start(() => this.scannerLineMove()); 349 | } 350 | } 351 | 352 | /** 353 | * 扫描界面 354 | */ 355 | export default class ZBarQRScannerView extends Component { 356 | static propTypes = { 357 | maskColor: React.PropTypes.string, 358 | borderColor: React.PropTypes.string, 359 | cornerColor: React.PropTypes.string, 360 | borderWidth: React.PropTypes.number, 361 | cornerBorderWidth: React.PropTypes.number, 362 | cornerBorderLength: React.PropTypes.number, 363 | rectHeight: React.PropTypes.number, 364 | rectWidth: React.PropTypes.number, 365 | isLoading: React.PropTypes.bool, 366 | isCornerOffset: React.PropTypes.bool,//边角是否偏移 367 | cornerOffsetSize: React.PropTypes.number, 368 | bottomMenuHeight: React.PropTypes.number, 369 | scanBarAnimateTime: React.PropTypes.number, 370 | scanBarColor: React.PropTypes.string, 371 | scanBarImage: React.PropTypes.any, 372 | scanBarHeight: React.PropTypes.number, 373 | scanBarMargin: React.PropTypes.number, 374 | hintText: React.PropTypes.string, 375 | hintTextStyle: React.PropTypes.object, 376 | hintTextPosition:React.PropTypes.number, 377 | renderTopBarView:React.PropTypes.func, 378 | renderBottomMenuView:React.PropTypes.func, 379 | isShowScanBar:React.PropTypes.bool, 380 | bottomMenuStyle:React.PropTypes.object, 381 | }; 382 | 383 | constructor(props) { 384 | super(props); 385 | //通过这句代码屏蔽 YellowBox 386 | console.disableYellowBox = true; 387 | } 388 | 389 | render() { 390 | return ( 391 | 392 | 396 | {/*绘制顶部标题栏组件*/} 397 | {this.props.renderTopBarView()} 398 | 399 | {/*绘制扫描遮罩*/} 400 | 423 | 424 | {/*绘制底部操作栏*/} 425 | 426 | {this.props.renderBottomMenuView()} 427 | 428 | 429 | 430 | 431 | ); 432 | } 433 | } 434 | 435 | 436 | const styles = StyleSheet.create({ 437 | buttonsContainer: { 438 | position: 'absolute', 439 | height: 100, 440 | bottom: 0, 441 | left: 0, 442 | right: 0, 443 | }, 444 | container: { 445 | alignItems: 'center', 446 | justifyContent: 'center', 447 | position: 'absolute', 448 | top: 0, 449 | right: 0, 450 | left: 0, 451 | }, 452 | viewfinder: { 453 | alignItems: 'center', 454 | justifyContent: 'center', 455 | }, 456 | topLeftCorner: { 457 | position: 'absolute', 458 | top: 0, 459 | left: 0, 460 | }, 461 | topRightCorner: { 462 | position: 'absolute', 463 | top: 0, 464 | right: 0, 465 | }, 466 | bottomLeftCorner: { 467 | position: 'absolute', 468 | bottom: 0, 469 | left: 0, 470 | }, 471 | bottomRightCorner: { 472 | position: 'absolute', 473 | bottom: 0, 474 | right: 0, 475 | }, 476 | topMask: { 477 | position: 'absolute', 478 | top: 0, 479 | }, 480 | leftMask: { 481 | position: 'absolute', 482 | left: 0, 483 | }, 484 | rightMask: { 485 | position: 'absolute', 486 | right: 0, 487 | }, 488 | bottomMask: { 489 | position: 'absolute', 490 | bottom: 0, 491 | } 492 | }); -------------------------------------------------------------------------------- /ZBarScannerView.js: -------------------------------------------------------------------------------- 1 | import React, { Component ,PropTypes} from 'react'; 2 | import { 3 | DeviceEventEmitter, // android 4 | NativeAppEventEmitter, // ios 5 | NativeModules, 6 | Platform, 7 | StyleSheet, 8 | requireNativeComponent, 9 | View, 10 | ViewPropTypes 11 | } from 'react-native'; 12 | 13 | const CameraManager = NativeModules.CameraManager || NativeModules.CameraModule; 14 | const CAMERA_REF = 'camera'; 15 | 16 | function convertNativeProps(props) { 17 | const newProps = { ...props }; 18 | if (typeof props.aspect === 'string') { 19 | newProps.aspect = Camera.constants.Aspect[props.aspect]; 20 | } 21 | 22 | if (typeof props.flashMode === 'string') { 23 | newProps.flashMode = Camera.constants.FlashMode[props.flashMode]; 24 | } 25 | 26 | if (typeof props.orientation === 'string') { 27 | newProps.orientation = Camera.constants.Orientation[props.orientation]; 28 | } 29 | 30 | if (typeof props.torchMode === 'string') { 31 | newProps.torchMode = Camera.constants.TorchMode[props.torchMode]; 32 | } 33 | 34 | if (typeof props.type === 'string') { 35 | newProps.type = Camera.constants.Type[props.type]; 36 | } 37 | 38 | if (typeof props.captureQuality === 'string') { 39 | newProps.captureQuality = Camera.constants.CaptureQuality[props.captureQuality]; 40 | } 41 | 42 | if (typeof props.captureMode === 'string') { 43 | newProps.captureMode = Camera.constants.CaptureMode[props.captureMode]; 44 | } 45 | 46 | if (typeof props.captureTarget === 'string') { 47 | newProps.captureTarget = Camera.constants.CaptureTarget[props.captureTarget]; 48 | } 49 | 50 | // do not register barCodeTypes if no barcode listener 51 | if (typeof props.onBarCodeRead !== 'function') { 52 | newProps.barCodeTypes = []; 53 | } 54 | 55 | newProps.barcodeScannerEnabled = typeof props.onBarCodeRead === 'function' 56 | 57 | return newProps; 58 | } 59 | 60 | export default class Camera extends Component { 61 | 62 | static constants = { 63 | Aspect: CameraManager.Aspect, 64 | BarCodeType: CameraManager.BarCodeType, 65 | Type: CameraManager.Type, 66 | CaptureMode: CameraManager.CaptureMode, 67 | CaptureTarget: CameraManager.CaptureTarget, 68 | CaptureQuality: CameraManager.CaptureQuality, 69 | Orientation: CameraManager.Orientation, 70 | FlashMode: CameraManager.FlashMode, 71 | TorchMode: CameraManager.TorchMode 72 | }; 73 | 74 | static propTypes = { 75 | ...ViewPropTypes, 76 | aspect: PropTypes.oneOfType([ 77 | PropTypes.string, 78 | PropTypes.number 79 | ]), 80 | captureAudio: PropTypes.bool, 81 | captureMode: PropTypes.oneOfType([ 82 | PropTypes.string, 83 | PropTypes.number 84 | ]), 85 | captureQuality: PropTypes.oneOfType([ 86 | PropTypes.string, 87 | PropTypes.number 88 | ]), 89 | captureTarget: PropTypes.oneOfType([ 90 | PropTypes.string, 91 | PropTypes.number 92 | ]), 93 | defaultOnFocusComponent: PropTypes.bool, 94 | flashMode: PropTypes.oneOfType([ 95 | PropTypes.string, 96 | PropTypes.number 97 | ]), 98 | keepAwake: PropTypes.bool, 99 | onBarCodeRead: PropTypes.func, 100 | barcodeScannerEnabled: PropTypes.bool, 101 | onFocusChanged: PropTypes.func, 102 | onZoomChanged: PropTypes.func, 103 | mirrorImage: PropTypes.bool, 104 | fixOrientation: PropTypes.bool, 105 | barCodeTypes: PropTypes.array, 106 | orientation: PropTypes.oneOfType([ 107 | PropTypes.string, 108 | PropTypes.number 109 | ]), 110 | playSoundOnCapture: PropTypes.bool, 111 | torchMode: PropTypes.oneOfType([ 112 | PropTypes.string, 113 | PropTypes.number 114 | ]), 115 | type: PropTypes.oneOfType([ 116 | PropTypes.string, 117 | PropTypes.number 118 | ]) 119 | }; 120 | 121 | static defaultProps = { 122 | aspect: CameraManager.Aspect.fill, 123 | type: CameraManager.Type.back, 124 | orientation: CameraManager.Orientation.auto, 125 | fixOrientation: false, 126 | captureAudio: false, 127 | captureMode: CameraManager.CaptureMode.still, 128 | captureTarget: CameraManager.CaptureTarget.cameraRoll, 129 | captureQuality: CameraManager.CaptureQuality.high, 130 | defaultOnFocusComponent: true, 131 | flashMode: CameraManager.FlashMode.off, 132 | playSoundOnCapture: true, 133 | torchMode: CameraManager.TorchMode.off, 134 | mirrorImage: false, 135 | barCodeTypes: Object.values(CameraManager.BarCodeType), 136 | }; 137 | 138 | static checkDeviceAuthorizationStatus = CameraManager.checkDeviceAuthorizationStatus; 139 | static checkVideoAuthorizationStatus = CameraManager.checkVideoAuthorizationStatus; 140 | static checkAudioAuthorizationStatus = CameraManager.checkAudioAuthorizationStatus; 141 | 142 | setNativeProps(props) { 143 | this.refs[CAMERA_REF].setNativeProps(props); 144 | } 145 | 146 | constructor() { 147 | super(); 148 | this.state = { 149 | isAuthorized: false, 150 | isRecording: false 151 | }; 152 | } 153 | 154 | async componentWillMount() { 155 | this._addOnBarCodeReadListener() 156 | 157 | let { captureMode } = convertNativeProps({ captureMode: this.props.captureMode }) 158 | let hasVideoAndAudio = this.props.captureAudio && captureMode === Camera.constants.CaptureMode.video 159 | let check = hasVideoAndAudio ? Camera.checkDeviceAuthorizationStatus : Camera.checkVideoAuthorizationStatus; 160 | 161 | if (check) { 162 | const isAuthorized = await check(); 163 | this.setState({ isAuthorized }); 164 | } 165 | } 166 | 167 | componentWillUnmount() { 168 | this._removeOnBarCodeReadListener() 169 | 170 | if (this.state.isRecording) { 171 | this.stopCapture(); 172 | } 173 | } 174 | 175 | componentWillReceiveProps(newProps) { 176 | const { onBarCodeRead } = this.props 177 | if (onBarCodeRead !== newProps.onBarCodeRead) { 178 | this._addOnBarCodeReadListener(newProps) 179 | } 180 | } 181 | 182 | _addOnBarCodeReadListener(props) { 183 | const { onBarCodeRead } = props || this.props 184 | this._removeOnBarCodeReadListener() 185 | if (onBarCodeRead) { 186 | this.cameraBarCodeReadListener = Platform.select({ 187 | ios: NativeAppEventEmitter.addListener('CameraBarCodeRead', this._onBarCodeRead), 188 | android: DeviceEventEmitter.addListener('CameraBarCodeReadAndroid', this._onBarCodeRead) 189 | }) 190 | } 191 | } 192 | _removeOnBarCodeReadListener() { 193 | const listener = this.cameraBarCodeReadListener 194 | if (listener) { 195 | listener.remove() 196 | } 197 | } 198 | 199 | render() { 200 | const style = [styles.base, this.props.style]; 201 | const nativeProps = convertNativeProps(this.props); 202 | 203 | return ; 204 | } 205 | 206 | _onBarCodeRead = (data) => { 207 | if (this.props.onBarCodeRead) { 208 | this.props.onBarCodeRead(data) 209 | } 210 | }; 211 | 212 | capture(options) { 213 | const props = convertNativeProps(this.props); 214 | options = { 215 | audio: props.captureAudio, 216 | barCodeTypes: props.barCodeTypes, 217 | mode: props.captureMode, 218 | playSoundOnCapture: props.playSoundOnCapture, 219 | target: props.captureTarget, 220 | quality: props.captureQuality, 221 | type: props.type, 222 | title: '', 223 | description: '', 224 | mirrorImage: props.mirrorImage, 225 | fixOrientation: props.fixOrientation, 226 | ...options 227 | }; 228 | 229 | if (options.mode === Camera.constants.CaptureMode.video) { 230 | options.totalSeconds = (options.totalSeconds > -1 ? options.totalSeconds : -1); 231 | options.preferredTimeScale = options.preferredTimeScale || 30; 232 | this.setState({ isRecording: true }); 233 | } 234 | 235 | return CameraManager.capture(options); 236 | } 237 | 238 | stopCapture() { 239 | if (this.state.isRecording) { 240 | this.setState({ isRecording: false }); 241 | return CameraManager.stopCapture(); 242 | } 243 | return Promise.resolve("Not Recording."); 244 | } 245 | 246 | getFOV() { 247 | return CameraManager.getFOV(); 248 | } 249 | 250 | hasFlash() { 251 | if (Platform.OS === 'android') { 252 | const props = convertNativeProps(this.props); 253 | return CameraManager.hasFlash({ 254 | type: props.type 255 | }); 256 | } 257 | return CameraManager.hasFlash(); 258 | } 259 | } 260 | 261 | export const constants = Camera.constants; 262 | 263 | const RCTCamera = requireNativeComponent('RCTZBarCamera', Camera, {nativeOnly: { 264 | testID: true, 265 | renderToHardwareTextureAndroid: true, 266 | accessibilityLabel: true, 267 | importantForAccessibility: true, 268 | accessibilityLiveRegion: true, 269 | accessibilityComponentType: true, 270 | onLayout: true 271 | }}); 272 | 273 | const styles = StyleSheet.create({ 274 | base: {}, 275 | }); 276 | -------------------------------------------------------------------------------- /android/BUCK: -------------------------------------------------------------------------------- 1 | # To learn about Buck see [Docs](https://buckbuild.com/). 2 | # To run your application with Buck: 3 | # - install Buck 4 | # - `npm start` - to start the packager 5 | # - `cd android` 6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"` 7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck 8 | # - `buck install -r android/app` - compile, install and run application 9 | # 10 | 11 | lib_deps = [] 12 | 13 | for jarfile in glob(['libs/*.jar']): 14 | name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')] 15 | lib_deps.append(':' + name) 16 | prebuilt_jar( 17 | name = name, 18 | binary_jar = jarfile, 19 | ) 20 | 21 | for aarfile in glob(['libs/*.aar']): 22 | name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')] 23 | lib_deps.append(':' + name) 24 | android_prebuilt_aar( 25 | name = name, 26 | aar = aarfile, 27 | ) 28 | 29 | android_library( 30 | name = "all-libs", 31 | exported_deps = lib_deps, 32 | ) 33 | 34 | android_library( 35 | name = "app-code", 36 | srcs = glob([ 37 | "src/main/java/**/*.java", 38 | ]), 39 | deps = [ 40 | ":all-libs", 41 | ":build_config", 42 | ":res", 43 | ], 44 | ) 45 | 46 | android_build_config( 47 | name = "build_config", 48 | package = "com.barcodescanner", 49 | ) 50 | 51 | android_resource( 52 | name = "res", 53 | package = "com.barcodescanner", 54 | res = "src/main/res", 55 | ) 56 | 57 | android_binary( 58 | name = "app", 59 | keystore = "//android/keystores:debug", 60 | manifest = "src/main/AndroidManifest.xml", 61 | package_type = "debug", 62 | deps = [ 63 | ":app-code", 64 | ], 65 | ) 66 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | jcenter() 4 | } 5 | 6 | dependencies { 7 | classpath 'com.android.tools.build:gradle:2.3.+' 8 | } 9 | } 10 | 11 | apply plugin: 'com.android.library' 12 | 13 | android { 14 | compileSdkVersion 26 15 | buildToolsVersion "26.0.1" 16 | 17 | defaultConfig { 18 | minSdkVersion 16 19 | targetSdkVersion 26 20 | versionCode 1 21 | versionName "1.0" 22 | } 23 | lintOptions { 24 | abortOnError false 25 | } 26 | } 27 | 28 | repositories { 29 | mavenCentral() 30 | } 31 | 32 | dependencies { 33 | compile "com.facebook.react:react-native:+" 34 | compile 'me.dm7.barcodescanner:zbar:1.9.8' 35 | compile "com.drewnoakes:metadata-extractor:2.9.1" 36 | } -------------------------------------------------------------------------------- /android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | 19 | # Disabling obfuscation is useful if you collect stack traces from production crashes 20 | # (unless you are using a system that supports de-obfuscate the stack traces). 21 | -dontobfuscate 22 | 23 | # React Native 24 | 25 | # Keep our interfaces so they can be used by other ProGuard rules. 26 | # See http://sourceforge.net/p/proguard/bugs/466/ 27 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip 28 | -keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters 29 | -keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip 30 | 31 | # Do not strip any method/class that is annotated with @DoNotStrip 32 | -keep @com.facebook.proguard.annotations.DoNotStrip class * 33 | -keep @com.facebook.common.internal.DoNotStrip class * 34 | -keepclassmembers class * { 35 | @com.facebook.proguard.annotations.DoNotStrip *; 36 | @com.facebook.common.internal.DoNotStrip *; 37 | } 38 | 39 | -keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * { 40 | void set*(***); 41 | *** get*(); 42 | } 43 | 44 | -keep class * extends com.facebook.react.bridge.JavaScriptModule { *; } 45 | -keep class * extends com.facebook.react.bridge.NativeModule { *; } 46 | -keepclassmembers,includedescriptorclasses class * { native ; } 47 | -keepclassmembers class * { @com.facebook.react.uimanager.UIProp ; } 48 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp ; } 49 | -keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup ; } 50 | 51 | -dontwarn com.facebook.react.** 52 | 53 | # TextLayoutBuilder uses a non-public Android constructor within StaticLayout. 54 | # See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details. 55 | -dontwarn android.text.StaticLayout 56 | 57 | # okhttp 58 | 59 | -keepattributes Signature 60 | -keepattributes *Annotation* 61 | -keep class okhttp3.** { *; } 62 | -keep interface okhttp3.** { *; } 63 | -dontwarn okhttp3.** 64 | 65 | # okio 66 | 67 | -keep class sun.misc.Unsafe { *; } 68 | -dontwarn java.nio.file.* 69 | -dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement 70 | -dontwarn okio.** 71 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/src/main/java/com/barcodescanner/MutableImage.java: -------------------------------------------------------------------------------- 1 | package com.barcodescanner; 2 | 3 | import android.graphics.Bitmap; 4 | import android.graphics.BitmapFactory; 5 | import android.graphics.Matrix; 6 | import android.media.ExifInterface; 7 | import android.util.Base64; 8 | import android.util.Log; 9 | 10 | import com.drew.imaging.ImageMetadataReader; 11 | import com.drew.imaging.ImageProcessingException; 12 | import com.drew.metadata.Directory; 13 | import com.drew.metadata.Metadata; 14 | import com.drew.metadata.MetadataException; 15 | import com.drew.metadata.Tag; 16 | import com.drew.metadata.exif.ExifIFD0Directory; 17 | import com.facebook.react.bridge.ReadableMap; 18 | 19 | import java.io.BufferedInputStream; 20 | import java.io.ByteArrayInputStream; 21 | import java.io.ByteArrayOutputStream; 22 | import java.io.File; 23 | import java.io.FileOutputStream; 24 | import java.io.IOException; 25 | 26 | public class MutableImage { 27 | private static final String TAG = "RNCamera"; 28 | 29 | private final byte[] originalImageData; 30 | private Bitmap currentRepresentation; 31 | private Metadata originalImageMetaData; 32 | private boolean hasBeenReoriented = false; 33 | 34 | public MutableImage(byte[] originalImageData) { 35 | this.originalImageData = originalImageData; 36 | this.currentRepresentation = toBitmap(originalImageData); 37 | } 38 | 39 | public void mirrorImage() throws ImageMutationFailedException { 40 | Matrix m = new Matrix(); 41 | 42 | m.preScale(-1, 1); 43 | 44 | Bitmap bitmap = Bitmap.createBitmap( 45 | currentRepresentation, 46 | 0, 47 | 0, 48 | currentRepresentation.getWidth(), 49 | currentRepresentation.getHeight(), 50 | m, 51 | false 52 | ); 53 | 54 | if (bitmap == null) 55 | throw new ImageMutationFailedException("failed to mirror"); 56 | 57 | this.currentRepresentation = bitmap; 58 | } 59 | 60 | public void fixOrientation() throws ImageMutationFailedException { 61 | try { 62 | Metadata metadata = originalImageMetaData(); 63 | 64 | ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class); 65 | if (exifIFD0Directory == null) { 66 | return; 67 | } else if (exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) { 68 | int exifOrientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION); 69 | if(exifOrientation != 1) { 70 | rotate(exifOrientation); 71 | exifIFD0Directory.setInt(ExifIFD0Directory.TAG_ORIENTATION, 1); 72 | } 73 | } 74 | } catch (ImageProcessingException | IOException | MetadataException e) { 75 | throw new ImageMutationFailedException("failed to fix orientation", e); 76 | } 77 | } 78 | 79 | //see http://www.impulseadventure.com/photo/exif-orientation.html 80 | private void rotate(int exifOrientation) throws ImageMutationFailedException { 81 | final Matrix bitmapMatrix = new Matrix(); 82 | switch (exifOrientation) { 83 | case 1: 84 | return;//no rotation required 85 | case 2: 86 | bitmapMatrix.postScale(-1, 1); 87 | break; 88 | case 3: 89 | bitmapMatrix.postRotate(180); 90 | break; 91 | case 4: 92 | bitmapMatrix.postRotate(180); 93 | bitmapMatrix.postScale(-1, 1); 94 | break; 95 | case 5: 96 | bitmapMatrix.postRotate(90); 97 | bitmapMatrix.postScale(-1, 1); 98 | break; 99 | case 6: 100 | bitmapMatrix.postRotate(90); 101 | break; 102 | case 7: 103 | bitmapMatrix.postRotate(270); 104 | bitmapMatrix.postScale(-1, 1); 105 | break; 106 | case 8: 107 | bitmapMatrix.postRotate(270); 108 | break; 109 | default: 110 | break; 111 | } 112 | 113 | Bitmap transformedBitmap = Bitmap.createBitmap( 114 | currentRepresentation, 115 | 0, 116 | 0, 117 | currentRepresentation.getWidth(), 118 | currentRepresentation.getHeight(), 119 | bitmapMatrix, 120 | false 121 | ); 122 | 123 | if (transformedBitmap == null) 124 | throw new ImageMutationFailedException("failed to rotate"); 125 | 126 | this.currentRepresentation = transformedBitmap; 127 | this.hasBeenReoriented = true; 128 | } 129 | 130 | private static Bitmap toBitmap(byte[] data) { 131 | try { 132 | ByteArrayInputStream inputStream = new ByteArrayInputStream(data); 133 | Bitmap photo = BitmapFactory.decodeStream(inputStream); 134 | inputStream.close(); 135 | return photo; 136 | } catch (IOException e) { 137 | throw new IllegalStateException("Will not happen", e); 138 | } 139 | } 140 | 141 | public String toBase64(int jpegQualityPercent) { 142 | return Base64.encodeToString(toJpeg(currentRepresentation, jpegQualityPercent), Base64.DEFAULT); 143 | } 144 | 145 | public void writeDataToFile(File file, ReadableMap options, int jpegQualityPercent) throws IOException { 146 | FileOutputStream fos = new FileOutputStream(file); 147 | fos.write(toJpeg(currentRepresentation, jpegQualityPercent)); 148 | fos.close(); 149 | 150 | try { 151 | ExifInterface exif = new ExifInterface(file.getAbsolutePath()); 152 | 153 | // copy original exif data to the output exif... 154 | // unfortunately, this Android ExifInterface class doesn't understand all the tags so we lose some 155 | for (Directory directory : originalImageMetaData().getDirectories()) { 156 | for (Tag tag : directory.getTags()) { 157 | int tagType = tag.getTagType(); 158 | Object object = directory.getObject(tagType); 159 | exif.setAttribute(tag.getTagName(), object.toString()); 160 | } 161 | } 162 | 163 | writeLocationExifData(options, exif); 164 | 165 | if(hasBeenReoriented) 166 | rewriteOrientation(exif); 167 | 168 | exif.saveAttributes(); 169 | } catch (ImageProcessingException | IOException e) { 170 | Log.e(TAG, "failed to save exif data", e); 171 | } 172 | } 173 | 174 | private void rewriteOrientation(ExifInterface exif) { 175 | exif.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(ExifInterface.ORIENTATION_NORMAL)); 176 | } 177 | 178 | private void writeLocationExifData(ReadableMap options, ExifInterface exif) { 179 | if(!options.hasKey("metadata")) 180 | return; 181 | 182 | ReadableMap metadata = options.getMap("metadata"); 183 | if (!metadata.hasKey("location")) 184 | return; 185 | 186 | ReadableMap location = metadata.getMap("location"); 187 | if(!location.hasKey("coords")) 188 | return; 189 | 190 | try { 191 | ReadableMap coords = location.getMap("coords"); 192 | double latitude = coords.getDouble("latitude"); 193 | double longitude = coords.getDouble("longitude"); 194 | 195 | GPS.writeExifData(latitude, longitude, exif); 196 | } catch (IOException e) { 197 | Log.e(TAG, "Couldn't write location data", e); 198 | } 199 | } 200 | 201 | private Metadata originalImageMetaData() throws ImageProcessingException, IOException { 202 | if(this.originalImageMetaData == null) {//this is expensive, don't do it more than once 203 | originalImageMetaData = ImageMetadataReader.readMetadata( 204 | new BufferedInputStream(new ByteArrayInputStream(originalImageData)), 205 | originalImageData.length 206 | ); 207 | } 208 | return originalImageMetaData; 209 | } 210 | 211 | private static byte[] toJpeg(Bitmap bitmap, int quality) throws OutOfMemoryError { 212 | ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); 213 | bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream); 214 | 215 | try { 216 | return outputStream.toByteArray(); 217 | } finally { 218 | try { 219 | outputStream.close(); 220 | } catch (IOException e) { 221 | Log.e(TAG, "problem compressing jpeg", e); 222 | } 223 | } 224 | } 225 | 226 | public static class ImageMutationFailedException extends Exception { 227 | public ImageMutationFailedException(String detailMessage, Throwable throwable) { 228 | super(detailMessage, throwable); 229 | } 230 | 231 | public ImageMutationFailedException(String detailMessage) { 232 | super(detailMessage); 233 | } 234 | } 235 | 236 | private static class GPS { 237 | public static void writeExifData(double latitude, double longitude, ExifInterface exif) throws IOException { 238 | exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, toDegreeMinuteSecods(latitude)); 239 | exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, latitudeRef(latitude)); 240 | exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, toDegreeMinuteSecods(longitude)); 241 | exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, longitudeRef(longitude)); 242 | } 243 | 244 | private static String latitudeRef(double latitude) { 245 | return latitude < 0.0d ? "S" : "N"; 246 | } 247 | 248 | private static String longitudeRef(double longitude) { 249 | return longitude < 0.0d ? "W" : "E"; 250 | } 251 | 252 | private static String toDegreeMinuteSecods(double latitude) { 253 | latitude = Math.abs(latitude); 254 | int degree = (int) latitude; 255 | latitude *= 60; 256 | latitude -= (degree * 60.0d); 257 | int minute = (int) latitude; 258 | latitude *= 60; 259 | latitude -= (minute * 60.0d); 260 | int second = (int) (latitude * 1000.0d); 261 | 262 | StringBuffer sb = new StringBuffer(); 263 | sb.append(degree); 264 | sb.append("/1,"); 265 | sb.append(minute); 266 | sb.append("/1,"); 267 | sb.append(second); 268 | sb.append("/1000,"); 269 | return sb.toString(); 270 | } 271 | } 272 | } 273 | -------------------------------------------------------------------------------- /android/src/main/java/com/barcodescanner/RCTCamera.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Fabrice Armisen (farmisen@gmail.com) on 1/4/16. 3 | */ 4 | 5 | package com.barcodescanner; 6 | 7 | import android.hardware.Camera; 8 | import android.media.CamcorderProfile; 9 | import android.util.Log; 10 | 11 | import java.util.HashMap; 12 | import java.util.List; 13 | import java.util.Map; 14 | 15 | public class RCTCamera { 16 | private static RCTCamera ourInstance; 17 | private final HashMap _cameraInfos; 18 | private final HashMap _cameraTypeToIndex; 19 | private final Map _cameras; 20 | private static final Resolution RESOLUTION_480P = new Resolution(853, 480); // 480p shoots for a 16:9 HD aspect ratio, but can otherwise fall back/down to any other supported camera sizes, such as 800x480 or 720x480, if (any) present. See getSupportedPictureSizes/getSupportedVideoSizes below. 21 | private static final Resolution RESOLUTION_720P = new Resolution(1280, 720); 22 | private static final Resolution RESOLUTION_1080P = new Resolution(1920, 1080); 23 | private boolean _barcodeScannerEnabled = false; 24 | private List _barCodeTypes = null; 25 | private int _orientation = -1; 26 | private int _actualDeviceOrientation = 0; 27 | private int _adjustedDeviceOrientation = 0; 28 | 29 | public static RCTCamera getInstance() { 30 | return ourInstance; 31 | } 32 | public static void createInstance(int deviceOrientation) { 33 | ourInstance = new RCTCamera(deviceOrientation); 34 | } 35 | 36 | 37 | public synchronized Camera acquireCameraInstance(int type) { 38 | if (null == _cameras.get(type) && null != _cameraTypeToIndex.get(type)) { 39 | try { 40 | Camera camera = Camera.open(_cameraTypeToIndex.get(type)); 41 | _cameras.put(type, camera); 42 | adjustPreviewLayout(type); 43 | } catch (Exception e) { 44 | Log.e("RCTCamera", "acquireCameraInstance failed", e); 45 | } 46 | } 47 | return _cameras.get(type); 48 | } 49 | 50 | public void releaseCameraInstance(int type) { 51 | // Release seems async and creates race conditions. Remove from map first before releasing. 52 | Camera releasingCamera = _cameras.get(type); 53 | if (null != releasingCamera) { 54 | _cameras.remove(type); 55 | releasingCamera.release(); 56 | } 57 | } 58 | 59 | public int getPreviewWidth(int type) { 60 | CameraInfoWrapper cameraInfo = _cameraInfos.get(type); 61 | if (null == cameraInfo) { 62 | return 0; 63 | } 64 | return cameraInfo.previewWidth; 65 | } 66 | 67 | public int getPreviewHeight(int type) { 68 | CameraInfoWrapper cameraInfo = _cameraInfos.get(type); 69 | if (null == cameraInfo) { 70 | return 0; 71 | } 72 | return cameraInfo.previewHeight; 73 | } 74 | 75 | public Camera.Size getBestSize(List supportedSizes, int maxWidth, int maxHeight) { 76 | Camera.Size bestSize = null; 77 | for (Camera.Size size : supportedSizes) { 78 | if (size.width > maxWidth || size.height > maxHeight) { 79 | continue; 80 | } 81 | 82 | if (bestSize == null) { 83 | bestSize = size; 84 | continue; 85 | } 86 | 87 | int resultArea = bestSize.width * bestSize.height; 88 | int newArea = size.width * size.height; 89 | 90 | if (newArea > resultArea) { 91 | bestSize = size; 92 | } 93 | } 94 | 95 | return bestSize; 96 | } 97 | 98 | private Camera.Size getSmallestSize(List supportedSizes) { 99 | Camera.Size smallestSize = null; 100 | for (Camera.Size size : supportedSizes) { 101 | if (smallestSize == null) { 102 | smallestSize = size; 103 | continue; 104 | } 105 | 106 | int resultArea = smallestSize.width * smallestSize.height; 107 | int newArea = size.width * size.height; 108 | 109 | if (newArea < resultArea) { 110 | smallestSize = size; 111 | } 112 | } 113 | 114 | return smallestSize; 115 | } 116 | 117 | private Camera.Size getClosestSize(List supportedSizes, int matchWidth, int matchHeight) { 118 | Camera.Size closestSize = null; 119 | for (Camera.Size size : supportedSizes) { 120 | if (closestSize == null) { 121 | closestSize = size; 122 | continue; 123 | } 124 | 125 | int currentDelta = Math.abs(closestSize.width - matchWidth) * Math.abs(closestSize.height - matchHeight); 126 | int newDelta = Math.abs(size.width - matchWidth) * Math.abs(size.height - matchHeight); 127 | 128 | if (newDelta < currentDelta) { 129 | closestSize = size; 130 | } 131 | } 132 | return closestSize; 133 | } 134 | 135 | protected List getSupportedVideoSizes(Camera camera) { 136 | Camera.Parameters params = camera.getParameters(); 137 | // defer to preview instead of params.getSupportedVideoSizes() http://bit.ly/1rxOsq0 138 | // but prefer SupportedVideoSizes! 139 | List sizes = params.getSupportedVideoSizes(); 140 | if (sizes != null) { 141 | return sizes; 142 | } 143 | 144 | // Video sizes may be null, which indicates that all the supported 145 | // preview sizes are supported for video recording. 146 | return params.getSupportedPreviewSizes(); 147 | } 148 | 149 | public int getOrientation() { 150 | return _orientation; 151 | } 152 | 153 | public void setOrientation(int orientation) { 154 | if (_orientation == orientation) { 155 | return; 156 | } 157 | _orientation = orientation; 158 | adjustPreviewLayout(RCTZBarCameraModule.RCT_CAMERA_TYPE_FRONT); 159 | adjustPreviewLayout(RCTZBarCameraModule.RCT_CAMERA_TYPE_BACK); 160 | } 161 | 162 | public boolean isBarcodeScannerEnabled() { 163 | return _barcodeScannerEnabled; 164 | } 165 | 166 | public void setBarcodeScannerEnabled(boolean barcodeScannerEnabled) { 167 | _barcodeScannerEnabled = barcodeScannerEnabled; 168 | } 169 | 170 | public List getBarCodeTypes() { 171 | return _barCodeTypes; 172 | } 173 | 174 | public void setBarCodeTypes(List barCodeTypes) { 175 | _barCodeTypes = barCodeTypes; 176 | } 177 | 178 | public int getActualDeviceOrientation() { 179 | return _actualDeviceOrientation; 180 | } 181 | 182 | public void setAdjustedDeviceOrientation(int orientation) { 183 | _adjustedDeviceOrientation = orientation; 184 | } 185 | 186 | public int getAdjustedDeviceOrientation() { 187 | return _adjustedDeviceOrientation; 188 | } 189 | 190 | public void setActualDeviceOrientation(int actualDeviceOrientation) { 191 | _actualDeviceOrientation = actualDeviceOrientation; 192 | adjustPreviewLayout(RCTZBarCameraModule.RCT_CAMERA_TYPE_FRONT); 193 | adjustPreviewLayout(RCTZBarCameraModule.RCT_CAMERA_TYPE_BACK); 194 | } 195 | 196 | public void setCaptureMode(final int cameraType, final int captureMode) { 197 | Camera camera = _cameras.get(cameraType); 198 | if (camera == null) { 199 | return; 200 | } 201 | 202 | // Set (video) recording hint based on camera type. For video recording, setting 203 | // this hint can help reduce the time it takes to start recording. 204 | Camera.Parameters parameters = camera.getParameters(); 205 | parameters.setRecordingHint(captureMode == RCTZBarCameraModule.RCT_CAMERA_CAPTURE_MODE_VIDEO); 206 | camera.setParameters(parameters); 207 | } 208 | 209 | public void setCaptureQuality(int cameraType, String captureQuality) { 210 | Camera camera = this.acquireCameraInstance(cameraType); 211 | if (camera == null) { 212 | return; 213 | } 214 | 215 | Camera.Parameters parameters = camera.getParameters(); 216 | Camera.Size pictureSize = null; 217 | List supportedSizes = parameters.getSupportedPictureSizes(); 218 | switch (captureQuality) { 219 | case RCTZBarCameraModule.RCT_CAMERA_CAPTURE_QUALITY_LOW: 220 | pictureSize = getSmallestSize(supportedSizes); 221 | break; 222 | case RCTZBarCameraModule.RCT_CAMERA_CAPTURE_QUALITY_MEDIUM: 223 | pictureSize = supportedSizes.get(supportedSizes.size() / 2); 224 | break; 225 | case RCTZBarCameraModule.RCT_CAMERA_CAPTURE_QUALITY_HIGH: 226 | pictureSize = getBestSize(parameters.getSupportedPictureSizes(), Integer.MAX_VALUE, Integer.MAX_VALUE); 227 | break; 228 | case RCTZBarCameraModule.RCT_CAMERA_CAPTURE_QUALITY_PREVIEW: 229 | Camera.Size optimalPreviewSize = getBestSize(parameters.getSupportedPreviewSizes(), Integer.MAX_VALUE, Integer.MAX_VALUE); 230 | pictureSize = getClosestSize(parameters.getSupportedPictureSizes(), optimalPreviewSize.width, optimalPreviewSize.height); 231 | break; 232 | case RCTZBarCameraModule.RCT_CAMERA_CAPTURE_QUALITY_480P: 233 | pictureSize = getBestSize(supportedSizes, RESOLUTION_480P.width, RESOLUTION_480P.height); 234 | break; 235 | case RCTZBarCameraModule.RCT_CAMERA_CAPTURE_QUALITY_720P: 236 | pictureSize = getBestSize(supportedSizes, RESOLUTION_720P.width, RESOLUTION_720P.height); 237 | break; 238 | case RCTZBarCameraModule.RCT_CAMERA_CAPTURE_QUALITY_1080P: 239 | pictureSize = getBestSize(supportedSizes, RESOLUTION_1080P.width, RESOLUTION_1080P.height); 240 | break; 241 | } 242 | 243 | if (pictureSize != null) { 244 | parameters.setPictureSize(pictureSize.width, pictureSize.height); 245 | camera.setParameters(parameters); 246 | } 247 | } 248 | 249 | public CamcorderProfile setCaptureVideoQuality(int cameraType, String captureQuality) { 250 | Camera camera = this.acquireCameraInstance(cameraType); 251 | if (camera == null) { 252 | return null; 253 | } 254 | 255 | Camera.Size videoSize = null; 256 | List supportedSizes = getSupportedVideoSizes(camera); 257 | CamcorderProfile cm = null; 258 | switch (captureQuality) { 259 | case RCTZBarCameraModule.RCT_CAMERA_CAPTURE_QUALITY_LOW: 260 | videoSize = getSmallestSize(supportedSizes); 261 | cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_480P); 262 | break; 263 | case RCTZBarCameraModule.RCT_CAMERA_CAPTURE_QUALITY_MEDIUM: 264 | videoSize = supportedSizes.get(supportedSizes.size() / 2); 265 | cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_720P); 266 | break; 267 | case RCTZBarCameraModule.RCT_CAMERA_CAPTURE_QUALITY_HIGH: 268 | videoSize = getBestSize(supportedSizes, Integer.MAX_VALUE, Integer.MAX_VALUE); 269 | cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_HIGH); 270 | break; 271 | case RCTZBarCameraModule.RCT_CAMERA_CAPTURE_QUALITY_480P: 272 | videoSize = getBestSize(supportedSizes, RESOLUTION_480P.width, RESOLUTION_480P.height); 273 | cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_480P); 274 | break; 275 | case RCTZBarCameraModule.RCT_CAMERA_CAPTURE_QUALITY_720P: 276 | videoSize = getBestSize(supportedSizes, RESOLUTION_720P.width, RESOLUTION_720P.height); 277 | cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_720P); 278 | break; 279 | case RCTZBarCameraModule.RCT_CAMERA_CAPTURE_QUALITY_1080P: 280 | videoSize = getBestSize(supportedSizes, RESOLUTION_1080P.width, RESOLUTION_1080P.height); 281 | cm = CamcorderProfile.get(_cameraTypeToIndex.get(cameraType), CamcorderProfile.QUALITY_1080P); 282 | break; 283 | } 284 | 285 | if (cm == null){ 286 | return null; 287 | } 288 | 289 | if (videoSize != null) { 290 | cm.videoFrameHeight = videoSize.height; 291 | cm.videoFrameWidth = videoSize.width; 292 | } 293 | 294 | return cm; 295 | } 296 | 297 | public void setTorchMode(int cameraType, int torchMode) { 298 | Camera camera = this.acquireCameraInstance(cameraType); 299 | if (null == camera) { 300 | return; 301 | } 302 | 303 | Camera.Parameters parameters = camera.getParameters(); 304 | String value = parameters.getFlashMode(); 305 | switch (torchMode) { 306 | case RCTZBarCameraModule.RCT_CAMERA_TORCH_MODE_ON: 307 | value = Camera.Parameters.FLASH_MODE_TORCH; 308 | break; 309 | case RCTZBarCameraModule.RCT_CAMERA_TORCH_MODE_OFF: 310 | value = Camera.Parameters.FLASH_MODE_OFF; 311 | break; 312 | } 313 | 314 | List flashModes = parameters.getSupportedFlashModes(); 315 | if (flashModes != null && flashModes.contains(value)) { 316 | parameters.setFlashMode(value); 317 | camera.setParameters(parameters); 318 | } 319 | } 320 | 321 | public void setFlashMode(int cameraType, int flashMode) { 322 | Camera camera = this.acquireCameraInstance(cameraType); 323 | if (null == camera) { 324 | return; 325 | } 326 | 327 | Camera.Parameters parameters = camera.getParameters(); 328 | String value = parameters.getFlashMode(); 329 | switch (flashMode) { 330 | case RCTZBarCameraModule.RCT_CAMERA_FLASH_MODE_AUTO: 331 | value = Camera.Parameters.FLASH_MODE_AUTO; 332 | break; 333 | case RCTZBarCameraModule.RCT_CAMERA_FLASH_MODE_ON: 334 | value = Camera.Parameters.FLASH_MODE_ON; 335 | break; 336 | case RCTZBarCameraModule.RCT_CAMERA_FLASH_MODE_OFF: 337 | value = Camera.Parameters.FLASH_MODE_OFF; 338 | break; 339 | } 340 | List flashModes = parameters.getSupportedFlashModes(); 341 | if (flashModes != null && flashModes.contains(value)) { 342 | parameters.setFlashMode(value); 343 | camera.setParameters(parameters); 344 | } 345 | } 346 | 347 | public void adjustCameraRotationToDeviceOrientation(int type, int deviceOrientation) { 348 | Camera camera = _cameras.get(type); 349 | if (null == camera) { 350 | return; 351 | } 352 | 353 | CameraInfoWrapper cameraInfo = _cameraInfos.get(type); 354 | int rotation; 355 | int orientation = cameraInfo.info.orientation; 356 | if (cameraInfo.info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 357 | rotation = (orientation + deviceOrientation * 90) % 360; 358 | } else { 359 | rotation = (orientation - deviceOrientation * 90 + 360) % 360; 360 | } 361 | cameraInfo.rotation = rotation; 362 | Camera.Parameters parameters = camera.getParameters(); 363 | parameters.setRotation(cameraInfo.rotation); 364 | 365 | try { 366 | camera.setParameters(parameters); 367 | } catch (Exception e) { 368 | e.printStackTrace(); 369 | } 370 | } 371 | 372 | public void adjustPreviewLayout(int type) { 373 | Camera camera = _cameras.get(type); 374 | if (null == camera) { 375 | return; 376 | } 377 | 378 | CameraInfoWrapper cameraInfo = _cameraInfos.get(type); 379 | int displayRotation; 380 | int rotation; 381 | int orientation = cameraInfo.info.orientation; 382 | if (cameraInfo.info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT) { 383 | rotation = (orientation + _actualDeviceOrientation * 90) % 360; 384 | displayRotation = (720 - orientation - _actualDeviceOrientation * 90) % 360; 385 | } else { 386 | rotation = (orientation - _actualDeviceOrientation * 90 + 360) % 360; 387 | displayRotation = rotation; 388 | } 389 | cameraInfo.rotation = rotation; 390 | // TODO: take in account the _orientation prop 391 | 392 | setAdjustedDeviceOrientation(rotation); 393 | camera.setDisplayOrientation(displayRotation); 394 | 395 | Camera.Parameters parameters = camera.getParameters(); 396 | parameters.setRotation(cameraInfo.rotation); 397 | 398 | // set preview size 399 | // defaults to highest resolution available 400 | Camera.Size optimalPreviewSize = getBestSize(parameters.getSupportedPreviewSizes(), Integer.MAX_VALUE, Integer.MAX_VALUE); 401 | int width = optimalPreviewSize.width; 402 | int height = optimalPreviewSize.height; 403 | 404 | parameters.setPreviewSize(width, height); 405 | try { 406 | camera.setParameters(parameters); 407 | } catch (Exception e) { 408 | e.printStackTrace(); 409 | } 410 | 411 | if (cameraInfo.rotation == 0 || cameraInfo.rotation == 180) { 412 | cameraInfo.previewWidth = width; 413 | cameraInfo.previewHeight = height; 414 | } else { 415 | cameraInfo.previewWidth = height; 416 | cameraInfo.previewHeight = width; 417 | } 418 | } 419 | 420 | private RCTCamera(int deviceOrientation) { 421 | _cameras = new HashMap<>(); 422 | _cameraInfos = new HashMap<>(); 423 | _cameraTypeToIndex = new HashMap<>(); 424 | 425 | _actualDeviceOrientation = deviceOrientation; 426 | 427 | // map camera types to camera indexes and collect cameras properties 428 | for (int i = 0; i < Camera.getNumberOfCameras(); i++) { 429 | Camera.CameraInfo info = new Camera.CameraInfo(); 430 | Camera.getCameraInfo(i, info); 431 | if (info.facing == Camera.CameraInfo.CAMERA_FACING_FRONT && _cameraInfos.get(RCTZBarCameraModule.RCT_CAMERA_TYPE_FRONT) == null) { 432 | _cameraInfos.put(RCTZBarCameraModule.RCT_CAMERA_TYPE_FRONT, new CameraInfoWrapper(info)); 433 | _cameraTypeToIndex.put(RCTZBarCameraModule.RCT_CAMERA_TYPE_FRONT, i); 434 | acquireCameraInstance(RCTZBarCameraModule.RCT_CAMERA_TYPE_FRONT); 435 | releaseCameraInstance(RCTZBarCameraModule.RCT_CAMERA_TYPE_FRONT); 436 | } else if (info.facing == Camera.CameraInfo.CAMERA_FACING_BACK && _cameraInfos.get(RCTZBarCameraModule.RCT_CAMERA_TYPE_BACK) == null) { 437 | _cameraInfos.put(RCTZBarCameraModule.RCT_CAMERA_TYPE_BACK, new CameraInfoWrapper(info)); 438 | _cameraTypeToIndex.put(RCTZBarCameraModule.RCT_CAMERA_TYPE_BACK, i); 439 | acquireCameraInstance(RCTZBarCameraModule.RCT_CAMERA_TYPE_BACK); 440 | releaseCameraInstance(RCTZBarCameraModule.RCT_CAMERA_TYPE_BACK); 441 | } 442 | } 443 | } 444 | 445 | private class CameraInfoWrapper { 446 | public final Camera.CameraInfo info; 447 | public int rotation = 0; 448 | public int previewWidth = -1; 449 | public int previewHeight = -1; 450 | 451 | public CameraInfoWrapper(Camera.CameraInfo info) { 452 | this.info = info; 453 | } 454 | } 455 | 456 | private static class Resolution { 457 | public int width; 458 | public int height; 459 | 460 | public Resolution(final int width, final int height) { 461 | this.width = width; 462 | this.height = height; 463 | } 464 | } 465 | } 466 | -------------------------------------------------------------------------------- /android/src/main/java/com/barcodescanner/RCTCameraUtils.java: -------------------------------------------------------------------------------- 1 | package com.barcodescanner; 2 | 3 | import android.graphics.Rect; 4 | import android.graphics.RectF; 5 | import android.hardware.Camera; 6 | import android.view.MotionEvent; 7 | 8 | public class RCTCameraUtils { 9 | private static final int FOCUS_AREA_MOTION_EVENT_EDGE_LENGTH = 100; 10 | private static final int FOCUS_AREA_WEIGHT = 1000; 11 | 12 | /** 13 | * Computes a Camera.Area corresponding to the new focus area to focus the camera on. This is 14 | * done by deriving a square around the center of a MotionEvent pointer (with side length equal 15 | * to FOCUS_AREA_MOTION_EVENT_EDGE_LENGTH), then transforming this rectangle's/square's 16 | * coordinates into the (-1000, 1000) coordinate system used for camera focus areas. 17 | * 18 | * Also note that we operate on RectF instances for the most part, to avoid any integer 19 | * division rounding errors going forward. We only round at the very end for playing into 20 | * the final focus areas list. 21 | * 22 | * @throws RuntimeException if unable to compute valid intersection between MotionEvent region 23 | * and SurfaceTexture region. 24 | */ 25 | protected static Camera.Area computeFocusAreaFromMotionEvent(final MotionEvent event, final int surfaceTextureWidth, final int surfaceTextureHeight) { 26 | // Get position of first touch pointer. 27 | final int pointerId = event.getPointerId(0); 28 | final int pointerIndex = event.findPointerIndex(pointerId); 29 | final float centerX = event.getX(pointerIndex); 30 | final float centerY = event.getY(pointerIndex); 31 | 32 | // Build event rect. Note that coordinates increase right and down, such that left <= right 33 | // and top <= bottom. 34 | final RectF eventRect = new RectF( 35 | centerX - FOCUS_AREA_MOTION_EVENT_EDGE_LENGTH, // left 36 | centerY - FOCUS_AREA_MOTION_EVENT_EDGE_LENGTH, // top 37 | centerX + FOCUS_AREA_MOTION_EVENT_EDGE_LENGTH, // right 38 | centerY + FOCUS_AREA_MOTION_EVENT_EDGE_LENGTH // bottom 39 | ); 40 | 41 | // Intersect this rect with the rect corresponding to the full area of the parent surface 42 | // texture, making sure we are not placing any amount of the eventRect outside the parent 43 | // surface's area. 44 | final RectF surfaceTextureRect = new RectF( 45 | (float) 0, // left 46 | (float) 0, // top 47 | (float) surfaceTextureWidth, // right 48 | (float) surfaceTextureHeight // bottom 49 | ); 50 | final boolean intersectSuccess = eventRect.intersect(surfaceTextureRect); 51 | if (!intersectSuccess) { 52 | throw new RuntimeException( 53 | "MotionEvent rect does not intersect with SurfaceTexture rect; unable to " + 54 | "compute focus area" 55 | ); 56 | } 57 | 58 | // Transform into (-1000, 1000) focus area coordinate system. See 59 | // https://developer.android.com/reference/android/hardware/Camera.Area.html. 60 | // Note that if this is ever changed to a Rect instead of RectF, be cautious of integer 61 | // division rounding! 62 | final RectF focusAreaRect = new RectF( 63 | (eventRect.left / surfaceTextureWidth) * 2000 - 1000, // left 64 | (eventRect.top / surfaceTextureHeight) * 2000 - 1000, // top 65 | (eventRect.right / surfaceTextureWidth) * 2000 - 1000, // right 66 | (eventRect.bottom / surfaceTextureHeight) * 2000 - 1000 // bottom 67 | ); 68 | Rect focusAreaRectRounded = new Rect(); 69 | focusAreaRect.round(focusAreaRectRounded); 70 | return new Camera.Area(focusAreaRectRounded, FOCUS_AREA_WEIGHT); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /android/src/main/java/com/barcodescanner/RCTCameraView.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Fabrice Armisen (farmisen@gmail.com) on 1/3/16. 3 | */ 4 | 5 | package com.barcodescanner; 6 | 7 | import android.content.Context; 8 | import android.hardware.SensorManager; 9 | import android.view.OrientationEventListener; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.view.WindowManager; 13 | 14 | import java.util.List; 15 | 16 | public class RCTCameraView extends ViewGroup { 17 | private final OrientationEventListener _orientationListener; 18 | private final Context _context; 19 | private RCTCameraViewFinder _viewFinder = null; 20 | private int _actualDeviceOrientation = -1; 21 | private int _aspect = RCTZBarCameraModule.RCT_CAMERA_ASPECT_FIT; 22 | private int _captureMode = RCTZBarCameraModule.RCT_CAMERA_CAPTURE_MODE_STILL; 23 | private String _captureQuality = "high"; 24 | private int _torchMode = -1; 25 | private int _flashMode = -1; 26 | 27 | public RCTCameraView(Context context) { 28 | super(context); 29 | this._context = context; 30 | RCTCamera.createInstance(getDeviceOrientation(context)); 31 | 32 | _orientationListener = new OrientationEventListener(context, SensorManager.SENSOR_DELAY_NORMAL) { 33 | @Override 34 | public void onOrientationChanged(int orientation) { 35 | if (setActualDeviceOrientation(_context)) { 36 | layoutViewFinder(); 37 | } 38 | } 39 | }; 40 | 41 | if (_orientationListener.canDetectOrientation()) { 42 | _orientationListener.enable(); 43 | } else { 44 | _orientationListener.disable(); 45 | } 46 | } 47 | 48 | @Override 49 | protected void onLayout(boolean changed, int left, int top, int right, int bottom) { 50 | layoutViewFinder(left, top, right, bottom); 51 | } 52 | 53 | @Override 54 | public void onViewAdded(View child) { 55 | if (this._viewFinder == child) return; 56 | // remove and readd view to make sure it is in the back. 57 | // @TODO figure out why there was a z order issue in the first place and fix accordingly. 58 | this.removeView(this._viewFinder); 59 | this.addView(this._viewFinder, 0); 60 | } 61 | 62 | public void setAspect(int aspect) { 63 | this._aspect = aspect; 64 | layoutViewFinder(); 65 | } 66 | 67 | public void setCameraType(final int type) { 68 | if (null != this._viewFinder) { 69 | this._viewFinder.setCameraType(type); 70 | RCTCamera.getInstance().adjustPreviewLayout(type); 71 | } else { 72 | _viewFinder = new RCTCameraViewFinder(_context, type); 73 | if (-1 != this._flashMode) { 74 | _viewFinder.setFlashMode(this._flashMode); 75 | } 76 | if (-1 != this._torchMode) { 77 | _viewFinder.setTorchMode(this._torchMode); 78 | } 79 | addView(_viewFinder); 80 | } 81 | } 82 | 83 | public void setCaptureMode(final int captureMode) { 84 | this._captureMode = captureMode; 85 | if (this._viewFinder != null) { 86 | this._viewFinder.setCaptureMode(captureMode); 87 | } 88 | } 89 | 90 | public void setCaptureQuality(String captureQuality) { 91 | this._captureQuality = captureQuality; 92 | if (this._viewFinder != null) { 93 | this._viewFinder.setCaptureQuality(captureQuality); 94 | } 95 | } 96 | 97 | public void setTorchMode(int torchMode) { 98 | this._torchMode = torchMode; 99 | if (this._viewFinder != null) { 100 | this._viewFinder.setTorchMode(torchMode); 101 | } 102 | } 103 | 104 | public void setFlashMode(int flashMode) { 105 | this._flashMode = flashMode; 106 | if (this._viewFinder != null) { 107 | this._viewFinder.setFlashMode(flashMode); 108 | } 109 | } 110 | 111 | public void setOrientation(int orientation) { 112 | RCTCamera.getInstance().setOrientation(orientation); 113 | if (this._viewFinder != null) { 114 | layoutViewFinder(); 115 | } 116 | } 117 | 118 | public void setBarcodeScannerEnabled(boolean barcodeScannerEnabled) { 119 | RCTCamera.getInstance().setBarcodeScannerEnabled(barcodeScannerEnabled); 120 | } 121 | 122 | public void setBarCodeTypes(List types) { 123 | RCTCamera.getInstance().setBarCodeTypes(types); 124 | } 125 | 126 | private boolean setActualDeviceOrientation(Context context) { 127 | int actualDeviceOrientation = getDeviceOrientation(context); 128 | if (_actualDeviceOrientation != actualDeviceOrientation) { 129 | _actualDeviceOrientation = actualDeviceOrientation; 130 | RCTCamera.getInstance().setActualDeviceOrientation(_actualDeviceOrientation); 131 | return true; 132 | } else { 133 | return false; 134 | } 135 | } 136 | 137 | private int getDeviceOrientation(Context context) { 138 | return ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay().getOrientation(); 139 | } 140 | 141 | private void layoutViewFinder() { 142 | layoutViewFinder(this.getLeft(), this.getTop(), this.getRight(), this.getBottom()); 143 | } 144 | 145 | private void layoutViewFinder(int left, int top, int right, int bottom) { 146 | if (null == _viewFinder) { 147 | return; 148 | } 149 | float width = right - left; 150 | float height = bottom - top; 151 | int viewfinderWidth; 152 | int viewfinderHeight; 153 | double ratio; 154 | switch (this._aspect) { 155 | case RCTZBarCameraModule.RCT_CAMERA_ASPECT_FIT: 156 | ratio = this._viewFinder.getRatio(); 157 | if (ratio * height > width) { 158 | viewfinderHeight = (int) (width / ratio); 159 | viewfinderWidth = (int) width; 160 | } else { 161 | viewfinderWidth = (int) (ratio * height); 162 | viewfinderHeight = (int) height; 163 | } 164 | break; 165 | case RCTZBarCameraModule.RCT_CAMERA_ASPECT_FILL: 166 | ratio = this._viewFinder.getRatio(); 167 | if (ratio * height < width) { 168 | viewfinderHeight = (int) (width / ratio); 169 | viewfinderWidth = (int) width; 170 | } else { 171 | viewfinderWidth = (int) (ratio * height); 172 | viewfinderHeight = (int) height; 173 | } 174 | break; 175 | default: 176 | viewfinderWidth = (int) width; 177 | viewfinderHeight = (int) height; 178 | } 179 | 180 | int viewFinderPaddingX = (int) ((width - viewfinderWidth) / 2); 181 | int viewFinderPaddingY = (int) ((height - viewfinderHeight) / 2); 182 | 183 | this._viewFinder.layout(viewFinderPaddingX, viewFinderPaddingY, viewFinderPaddingX + viewfinderWidth, viewFinderPaddingY + viewfinderHeight); 184 | this.postInvalidate(this.getLeft(), this.getTop(), this.getRight(), this.getBottom()); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /android/src/main/java/com/barcodescanner/RCTCameraViewFinder.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Fabrice Armisen (farmisen@gmail.com) on 1/3/16. 3 | */ 4 | 5 | package com.barcodescanner; 6 | 7 | import android.content.Context; 8 | import android.graphics.SurfaceTexture; 9 | import android.hardware.Camera; 10 | import android.os.AsyncTask; 11 | import android.os.Build; 12 | import android.text.TextUtils; 13 | import android.view.MotionEvent; 14 | import android.view.TextureView; 15 | 16 | import com.facebook.react.bridge.Arguments; 17 | import com.facebook.react.bridge.ReactContext; 18 | import com.facebook.react.bridge.WritableMap; 19 | import com.facebook.react.modules.core.DeviceEventManagerModule; 20 | 21 | import net.sourceforge.zbar.Image; 22 | import net.sourceforge.zbar.ImageScanner; 23 | import net.sourceforge.zbar.Symbol; 24 | import net.sourceforge.zbar.SymbolSet; 25 | 26 | import java.nio.charset.StandardCharsets; 27 | import java.util.ArrayList; 28 | import java.util.Iterator; 29 | import java.util.List; 30 | 31 | import me.dm7.barcodescanner.zbar.BarcodeFormat; 32 | import me.dm7.barcodescanner.zbar.Result; 33 | 34 | 35 | class RCTCameraViewFinder extends TextureView implements TextureView.SurfaceTextureListener, Camera.PreviewCallback { 36 | private int _cameraType; 37 | private int _captureMode; 38 | private SurfaceTexture _surfaceTexture; 39 | private int _surfaceTextureWidth; 40 | private int _surfaceTextureHeight; 41 | private boolean _isStarting; 42 | private boolean _isStopping; 43 | private Camera _camera; 44 | private float mFingerSpacing; 45 | 46 | 47 | // concurrency lock for barcode scanner to avoid flooding the runtime 48 | public static volatile boolean barcodeScannerTaskLock = false; 49 | 50 | // reader instance for the barcode scanner 51 | private final ImageScanner mScanner = new ImageScanner(); 52 | 53 | public RCTCameraViewFinder(Context context, int type) { 54 | super(context); 55 | this.setSurfaceTextureListener(this); 56 | this._cameraType = type; 57 | this.setupScanner(); 58 | } 59 | 60 | @Override 61 | public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { 62 | _surfaceTexture = surface; 63 | _surfaceTextureWidth = width; 64 | _surfaceTextureHeight = height; 65 | startCamera(); 66 | } 67 | 68 | @Override 69 | public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) { 70 | _surfaceTextureWidth = width; 71 | _surfaceTextureHeight = height; 72 | } 73 | 74 | @Override 75 | public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { 76 | _surfaceTexture = null; 77 | _surfaceTextureWidth = 0; 78 | _surfaceTextureHeight = 0; 79 | stopCamera(); 80 | return true; 81 | } 82 | 83 | @Override 84 | public void onSurfaceTextureUpdated(SurfaceTexture surface) { 85 | } 86 | 87 | public double getRatio() { 88 | int width = RCTCamera.getInstance().getPreviewWidth(this._cameraType); 89 | int height = RCTCamera.getInstance().getPreviewHeight(this._cameraType); 90 | return ((float) width) / ((float) height); 91 | } 92 | 93 | public void setCameraType(final int type) { 94 | if (this._cameraType == type) { 95 | return; 96 | } 97 | new Thread(new Runnable() { 98 | @Override 99 | public void run() { 100 | stopPreview(); 101 | _cameraType = type; 102 | startPreview(); 103 | } 104 | }).start(); 105 | } 106 | 107 | public void setCaptureMode(final int captureMode) { 108 | RCTCamera.getInstance().setCaptureMode(_cameraType, captureMode); 109 | this._captureMode = captureMode; 110 | } 111 | 112 | public void setCaptureQuality(String captureQuality) { 113 | RCTCamera.getInstance().setCaptureQuality(_cameraType, captureQuality); 114 | } 115 | 116 | public void setTorchMode(int torchMode) { 117 | RCTCamera.getInstance().setTorchMode(_cameraType, torchMode); 118 | } 119 | 120 | public void setFlashMode(int flashMode) { 121 | RCTCamera.getInstance().setFlashMode(_cameraType, flashMode); 122 | } 123 | 124 | private void startPreview() { 125 | if (_surfaceTexture != null) { 126 | startCamera(); 127 | } 128 | } 129 | 130 | private void stopPreview() { 131 | if (_camera != null) { 132 | stopCamera(); 133 | } 134 | } 135 | 136 | synchronized private void startCamera() { 137 | if (!_isStarting) { 138 | _isStarting = true; 139 | try { 140 | _camera = RCTCamera.getInstance().acquireCameraInstance(_cameraType); 141 | Camera.Parameters parameters = _camera.getParameters(); 142 | 143 | final boolean isCaptureModeStill = (_captureMode == RCTZBarCameraModule.RCT_CAMERA_CAPTURE_MODE_STILL); 144 | final boolean isCaptureModeVideo = (_captureMode == RCTZBarCameraModule.RCT_CAMERA_CAPTURE_MODE_VIDEO); 145 | if (!isCaptureModeStill && !isCaptureModeVideo) { 146 | throw new RuntimeException("Unsupported capture mode:" + _captureMode); 147 | } 148 | 149 | // Set auto-focus. Try to set to continuous picture/video, and fall back to general 150 | // auto if available. 151 | List focusModes = parameters.getSupportedFocusModes(); 152 | if (isCaptureModeStill && focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE)) { 153 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_PICTURE); 154 | } else if (isCaptureModeVideo && focusModes.contains(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO)) { 155 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_CONTINUOUS_VIDEO); 156 | } else if (focusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { 157 | parameters.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); 158 | } 159 | 160 | // set picture size 161 | // defaults to max available size 162 | List supportedSizes; 163 | if (isCaptureModeStill) { 164 | supportedSizes = parameters.getSupportedPictureSizes(); 165 | } else if (isCaptureModeVideo) { 166 | supportedSizes = RCTCamera.getInstance().getSupportedVideoSizes(_camera); 167 | } else { 168 | throw new RuntimeException("Unsupported capture mode:" + _captureMode); 169 | } 170 | Camera.Size optimalPictureSize = RCTCamera.getInstance().getBestSize( 171 | supportedSizes, 172 | Integer.MAX_VALUE, 173 | Integer.MAX_VALUE 174 | ); 175 | parameters.setPictureSize(optimalPictureSize.width, optimalPictureSize.height); 176 | 177 | _camera.setParameters(parameters); 178 | _camera.setPreviewTexture(_surfaceTexture); 179 | _camera.startPreview(); 180 | // send previews to `onPreviewFrame` 181 | _camera.setPreviewCallback(this); 182 | } catch (NullPointerException e) { 183 | e.printStackTrace(); 184 | } catch (Exception e) { 185 | e.printStackTrace(); 186 | stopCamera(); 187 | } finally { 188 | _isStarting = false; 189 | } 190 | } 191 | } 192 | 193 | synchronized private void stopCamera() { 194 | if (!_isStopping) { 195 | _isStopping = true; 196 | try { 197 | if (_camera != null) { 198 | _camera.stopPreview(); 199 | // stop sending previews to `onPreviewFrame` 200 | _camera.setPreviewCallback(null); 201 | RCTCamera.getInstance().releaseCameraInstance(_cameraType); 202 | _camera = null; 203 | } 204 | 205 | } catch (Exception e) { 206 | e.printStackTrace(); 207 | } finally { 208 | _isStopping = false; 209 | } 210 | } 211 | } 212 | 213 | /** 214 | * Parse barcodes as BarcodeFormat constants. 215 | * 216 | * Supports all iOS codes except [code39mod43, itf14] 217 | * 218 | * Additionally supports [codabar, maxicode, rss14, rssexpanded, upca, upceanextension] 219 | */ 220 | private void setupScanner() { 221 | this.mScanner.setConfig(0, 256, 3); 222 | this.mScanner.setConfig(0, 257, 3); 223 | this.mScanner.setConfig(0, 0, 0); 224 | Iterator i$ = BarcodeFormat.ALL_FORMATS.iterator(); 225 | 226 | while (i$.hasNext()) { 227 | BarcodeFormat format = (BarcodeFormat) i$.next(); 228 | this.mScanner.setConfig(format.getId(), 0, 1); 229 | } 230 | } 231 | 232 | /** 233 | * Spawn a barcode reader task if 234 | * - the barcode scanner is enabled (has a onBarCodeRead function) 235 | * - one isn't already running 236 | * 237 | * See {Camera.PreviewCallback} 238 | */ 239 | public void onPreviewFrame(byte[] data, Camera camera) { 240 | if (RCTCamera.getInstance().isBarcodeScannerEnabled() && !RCTCameraViewFinder.barcodeScannerTaskLock) { 241 | RCTCameraViewFinder.barcodeScannerTaskLock = true; 242 | new ReaderAsyncTask(camera, data).execute(); 243 | } 244 | } 245 | 246 | private class ReaderAsyncTask extends AsyncTask { 247 | private byte[] imageData; 248 | private final Camera camera; 249 | 250 | ReaderAsyncTask(Camera camera, byte[] imageData) { 251 | this.camera = camera; 252 | this.imageData = imageData; 253 | } 254 | 255 | @Override 256 | protected Void doInBackground(Void... ignored) { 257 | if (isCancelled()) { 258 | return null; 259 | } 260 | 261 | Camera.Size size = camera.getParameters().getPreviewSize(); 262 | 263 | int width = size.width; 264 | int height = size.height; 265 | 266 | // rotate for zxing if orientation is portrait 267 | if (RCTCamera.getInstance().getActualDeviceOrientation() == 0) { 268 | byte[] rotated = new byte[imageData.length]; 269 | for (int y = 0; y < height; y++) { 270 | for (int x = 0; x < width; x++) { 271 | rotated[x * height + height - y - 1] = imageData[x + y * width]; 272 | } 273 | } 274 | width = size.height; 275 | height = size.width; 276 | imageData = rotated; 277 | } 278 | 279 | try { 280 | Image barcode = new Image(width, height, "Y800"); 281 | barcode.setData(imageData); 282 | int r = mScanner.scanImage(barcode); 283 | if (r != 0) { 284 | SymbolSet syms = mScanner.getResults(); 285 | final Result result = new Result(); 286 | Iterator i$ = syms.iterator(); 287 | 288 | while (i$.hasNext()) { 289 | Symbol sym = (Symbol) i$.next(); 290 | String symData; 291 | if (Build.VERSION.SDK_INT >= 19) { 292 | symData = new String(sym.getDataBytes(), StandardCharsets.UTF_8); 293 | } else { 294 | symData = sym.getData(); 295 | } 296 | 297 | if (!TextUtils.isEmpty(symData)) { 298 | result.setContents(symData); 299 | result.setBarcodeFormat(BarcodeFormat.getFormatById(sym.getType())); 300 | break; 301 | } 302 | } 303 | 304 | 305 | if (result != null) { 306 | ReactContext reactContext = RCTZBarCameraModule.getReactContextSingleton(); 307 | WritableMap event = Arguments.createMap(); 308 | event.putString("data", result.getContents()); 309 | event.putString("type", result.getBarcodeFormat().getName()); 310 | reactContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("CameraBarCodeReadAndroid", event); 311 | 312 | } 313 | 314 | } 315 | 316 | } catch (Throwable t) { 317 | // meh 318 | } finally { 319 | RCTCameraViewFinder.barcodeScannerTaskLock = false; 320 | return null; 321 | } 322 | } 323 | } 324 | 325 | @Override 326 | public boolean onTouchEvent(MotionEvent event) { 327 | // Get the pointer ID 328 | Camera.Parameters params = _camera.getParameters(); 329 | int action = event.getAction(); 330 | 331 | 332 | if (event.getPointerCount() > 1) { 333 | // handle multi-touch events 334 | if (action == MotionEvent.ACTION_POINTER_DOWN) { 335 | mFingerSpacing = getFingerSpacing(event); 336 | } else if (action == MotionEvent.ACTION_MOVE && params.isZoomSupported()) { 337 | _camera.cancelAutoFocus(); 338 | handleZoom(event, params); 339 | } 340 | } else { 341 | // handle single touch events 342 | if (action == MotionEvent.ACTION_UP) { 343 | handleFocus(event, params); 344 | } 345 | } 346 | return true; 347 | } 348 | 349 | private void handleZoom(MotionEvent event, Camera.Parameters params) { 350 | int maxZoom = params.getMaxZoom(); 351 | int zoom = params.getZoom(); 352 | float newDist = getFingerSpacing(event); 353 | if (newDist > mFingerSpacing) { 354 | //zoom in 355 | if (zoom < maxZoom) 356 | zoom++; 357 | } else if (newDist < mFingerSpacing) { 358 | //zoom out 359 | if (zoom > 0) 360 | zoom--; 361 | } 362 | mFingerSpacing = newDist; 363 | params.setZoom(zoom); 364 | _camera.setParameters(params); 365 | } 366 | 367 | /** 368 | * Handles setting focus to the location of the event. 369 | * 370 | * Note that this will override the focus mode on the camera to FOCUS_MODE_AUTO if available, 371 | * even if this was previously something else (such as FOCUS_MODE_CONTINUOUS_*; see also 372 | * {@link #startCamera()}. However, this makes sense - after the user has initiated any 373 | * specific focus intent, we shouldn't be refocusing and overriding their request! 374 | */ 375 | public void handleFocus(MotionEvent event, Camera.Parameters params) { 376 | List supportedFocusModes = params.getSupportedFocusModes(); 377 | if (supportedFocusModes != null && supportedFocusModes.contains(Camera.Parameters.FOCUS_MODE_AUTO)) { 378 | // Ensure focus areas are enabled. If max num focus areas is 0, then focus area is not 379 | // supported, so we cannot do anything here. 380 | if (params.getMaxNumFocusAreas() == 0) { 381 | return; 382 | } 383 | 384 | // Cancel any previous focus actions. 385 | _camera.cancelAutoFocus(); 386 | 387 | // Compute focus area rect. 388 | Camera.Area focusAreaFromMotionEvent; 389 | try { 390 | focusAreaFromMotionEvent = RCTCameraUtils.computeFocusAreaFromMotionEvent(event, _surfaceTextureWidth, _surfaceTextureHeight); 391 | } catch (final RuntimeException e) { 392 | e.printStackTrace(); 393 | return; 394 | } 395 | 396 | // Set focus mode to auto. 397 | params.setFocusMode(Camera.Parameters.FOCUS_MODE_AUTO); 398 | // Set focus area. 399 | final ArrayList focusAreas = new ArrayList(); 400 | focusAreas.add(focusAreaFromMotionEvent); 401 | params.setFocusAreas(focusAreas); 402 | 403 | // Also set metering area if enabled. If max num metering areas is 0, then metering area 404 | // is not supported. We can usually safely omit this anyway, though. 405 | if (params.getMaxNumMeteringAreas() > 0) { 406 | params.setMeteringAreas(focusAreas); 407 | } 408 | 409 | // Set parameters before starting auto-focus. 410 | _camera.setParameters(params); 411 | 412 | // Start auto-focus now that focus area has been set. If successful, then can cancel 413 | // it afterwards. Wrap in try-catch to avoid crashing on merely autoFocus fails. 414 | try { 415 | _camera.autoFocus(new Camera.AutoFocusCallback() { 416 | @Override 417 | public void onAutoFocus(boolean success, Camera camera) { 418 | if (success) { 419 | camera.cancelAutoFocus(); 420 | } 421 | } 422 | }); 423 | } catch (Exception e) { 424 | e.printStackTrace(); 425 | } 426 | } 427 | } 428 | 429 | /** Determine the space between the first two fingers */ 430 | private float getFingerSpacing(MotionEvent event) { 431 | float x = event.getX(0) - event.getX(1); 432 | float y = event.getY(0) - event.getY(1); 433 | return (float) Math.sqrt(x * x + y * y); 434 | } 435 | } 436 | -------------------------------------------------------------------------------- /android/src/main/java/com/barcodescanner/RCTCameraViewManager.java: -------------------------------------------------------------------------------- 1 | package com.barcodescanner; 2 | 3 | import com.facebook.react.bridge.ReadableArray; 4 | import com.facebook.react.uimanager.ThemedReactContext; 5 | import com.facebook.react.uimanager.ViewGroupManager; 6 | import com.facebook.react.uimanager.annotations.ReactProp; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | public class RCTCameraViewManager extends ViewGroupManager { 12 | private static final String REACT_CLASS = "RCTZBarCamera"; 13 | 14 | @Override 15 | public String getName() { 16 | return REACT_CLASS; 17 | } 18 | 19 | @Override 20 | public RCTCameraView createViewInstance(ThemedReactContext context) { 21 | return new RCTCameraView(context); 22 | } 23 | 24 | @ReactProp(name = "aspect") 25 | public void setAspect(RCTCameraView view, int aspect) { 26 | view.setAspect(aspect); 27 | } 28 | 29 | @ReactProp(name = "captureMode") 30 | public void setCaptureMode(RCTCameraView view, final int captureMode) { 31 | // Note that this in practice only performs any additional setup necessary for each mode; 32 | // the actual indication to capture a still or record a video when capture() is called is 33 | // still ultimately decided upon by what it in the options sent to capture(). 34 | view.setCaptureMode(captureMode); 35 | } 36 | 37 | @ReactProp(name = "captureTarget") 38 | public void setCaptureTarget(RCTCameraView view, int captureTarget) { 39 | // No reason to handle this props value here since it's passed again to the RCTCameraModule capture method 40 | } 41 | 42 | @ReactProp(name = "type") 43 | public void setType(RCTCameraView view, int type) { 44 | view.setCameraType(type); 45 | } 46 | 47 | @ReactProp(name = "captureQuality") 48 | public void setCaptureQuality(RCTCameraView view, String captureQuality) { 49 | view.setCaptureQuality(captureQuality); 50 | } 51 | 52 | @ReactProp(name = "torchMode") 53 | public void setTorchMode(RCTCameraView view, int torchMode) { 54 | view.setTorchMode(torchMode); 55 | } 56 | 57 | @ReactProp(name = "flashMode") 58 | public void setFlashMode(RCTCameraView view, int flashMode) { 59 | view.setFlashMode(flashMode); 60 | } 61 | 62 | @ReactProp(name = "orientation") 63 | public void setOrientation(RCTCameraView view, int orientation) { 64 | view.setOrientation(orientation); 65 | } 66 | 67 | @ReactProp(name = "captureAudio") 68 | public void setCaptureAudio(RCTCameraView view, boolean captureAudio) { 69 | // TODO - implement video mode 70 | } 71 | 72 | @ReactProp(name = "barcodeScannerEnabled") 73 | public void setBarcodeScannerEnabled(RCTCameraView view, boolean barcodeScannerEnabled) { 74 | view.setBarcodeScannerEnabled(barcodeScannerEnabled); 75 | } 76 | 77 | @ReactProp(name = "barCodeTypes") 78 | public void setBarCodeTypes(RCTCameraView view, ReadableArray barCodeTypes) { 79 | if (barCodeTypes == null) { 80 | return; 81 | } 82 | List result = new ArrayList(barCodeTypes.size()); 83 | for (int i = 0; i < barCodeTypes.size(); i++) { 84 | result.add(barCodeTypes.getString(i)); 85 | } 86 | view.setBarCodeTypes(result); 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /android/src/main/java/com/barcodescanner/RCTSensorOrientationChecker.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by rpopovici on 23/03/16. 3 | */ 4 | 5 | package com.barcodescanner; 6 | 7 | import android.content.Context; 8 | import android.hardware.Sensor; 9 | import android.hardware.SensorEvent; 10 | import android.hardware.SensorEventListener; 11 | import android.hardware.SensorManager; 12 | import android.view.Surface; 13 | 14 | import com.facebook.react.bridge.ReactApplicationContext; 15 | 16 | interface RCTSensorOrientationListener { 17 | void orientationEvent(); 18 | } 19 | 20 | public class RCTSensorOrientationChecker { 21 | 22 | int mOrientation = 0; 23 | private SensorEventListener mSensorEventListener; 24 | private SensorManager mSensorManager; 25 | private RCTSensorOrientationListener mListener = null; 26 | 27 | public RCTSensorOrientationChecker( ReactApplicationContext reactContext) { 28 | mSensorEventListener = new Listener(); 29 | mSensorManager = (SensorManager) reactContext.getSystemService(Context.SENSOR_SERVICE); 30 | 31 | } 32 | 33 | /** 34 | * Call on activity onResume() 35 | */ 36 | public void onResume() { 37 | mSensorManager.registerListener(mSensorEventListener, mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL); 38 | } 39 | 40 | /** 41 | * Call on activity onPause() 42 | */ 43 | public void onPause() { 44 | mSensorManager.unregisterListener(mSensorEventListener); 45 | } 46 | 47 | private class Listener implements SensorEventListener { 48 | 49 | @Override 50 | public void onSensorChanged(SensorEvent event) { 51 | float x = event.values[0]; 52 | float y = event.values[1]; 53 | 54 | if (x<5 && x>-5 && y > 5) 55 | mOrientation = Surface.ROTATION_0; // portrait 56 | else if (x<-5 && y<5 && y>-5) 57 | mOrientation = Surface.ROTATION_270; // right 58 | else if (x<5 && x>-5 && y<-5) 59 | mOrientation = Surface.ROTATION_180; // upside down 60 | else if (x>5 && y<5 && y>-5) 61 | mOrientation = Surface.ROTATION_90; // left 62 | 63 | if (mListener != null) { 64 | mListener.orientationEvent(); 65 | } 66 | } 67 | 68 | @Override 69 | public void onAccuracyChanged(Sensor sensor, int accuracy) { 70 | 71 | } 72 | } 73 | 74 | public int getOrientation() { 75 | return mOrientation; 76 | } 77 | 78 | public void registerOrientationListener(RCTSensorOrientationListener listener) { 79 | this.mListener = listener; 80 | } 81 | 82 | public void unregisterOrientationListener() { 83 | mListener = null; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /android/src/main/java/com/barcodescanner/RCTZBarCameraModule.java: -------------------------------------------------------------------------------- 1 | /** 2 | * Created by Fabrice Armisen (farmisen@gmail.com) on 1/4/16. 3 | * Android video recording support by Marc Johnson (me@marc.mn) 4/2016 4 | */ 5 | 6 | package com.barcodescanner; 7 | 8 | import android.content.ContentValues; 9 | import android.hardware.Camera; 10 | import android.media.CamcorderProfile; 11 | import android.media.MediaActionSound; 12 | import android.media.MediaRecorder; 13 | import android.media.MediaScannerConnection; 14 | import android.net.Uri; 15 | import android.os.AsyncTask; 16 | import android.os.Environment; 17 | import android.provider.MediaStore; 18 | import android.util.Base64; 19 | import android.util.Log; 20 | import android.view.Surface; 21 | 22 | import com.facebook.react.bridge.LifecycleEventListener; 23 | import com.facebook.react.bridge.Promise; 24 | import com.facebook.react.bridge.ReactApplicationContext; 25 | import com.facebook.react.bridge.ReactContextBaseJavaModule; 26 | import com.facebook.react.bridge.ReactMethod; 27 | import com.facebook.react.bridge.ReadableMap; 28 | import com.facebook.react.bridge.WritableMap; 29 | import com.facebook.react.bridge.WritableNativeMap; 30 | 31 | import java.io.ByteArrayOutputStream; 32 | import java.io.File; 33 | import java.io.FileInputStream; 34 | import java.io.IOException; 35 | import java.io.InputStream; 36 | import java.text.SimpleDateFormat; 37 | import java.util.Collections; 38 | import java.util.Date; 39 | import java.util.HashMap; 40 | import java.util.List; 41 | import java.util.Map; 42 | 43 | import javax.annotation.Nullable; 44 | 45 | public class RCTZBarCameraModule extends ReactContextBaseJavaModule 46 | implements MediaRecorder.OnInfoListener, MediaRecorder.OnErrorListener, LifecycleEventListener { 47 | private static final String TAG = "RCTCameraModule"; 48 | 49 | public static final int RCT_CAMERA_ASPECT_FILL = 0; 50 | public static final int RCT_CAMERA_ASPECT_FIT = 1; 51 | public static final int RCT_CAMERA_ASPECT_STRETCH = 2; 52 | public static final int RCT_CAMERA_CAPTURE_MODE_STILL = 0; 53 | public static final int RCT_CAMERA_CAPTURE_MODE_VIDEO = 1; 54 | public static final int RCT_CAMERA_CAPTURE_TARGET_MEMORY = 0; 55 | public static final int RCT_CAMERA_CAPTURE_TARGET_DISK = 1; 56 | public static final int RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL = 2; 57 | public static final int RCT_CAMERA_CAPTURE_TARGET_TEMP = 3; 58 | public static final int RCT_CAMERA_ORIENTATION_AUTO = Integer.MAX_VALUE; 59 | public static final int RCT_CAMERA_ORIENTATION_PORTRAIT = Surface.ROTATION_0; 60 | public static final int RCT_CAMERA_ORIENTATION_PORTRAIT_UPSIDE_DOWN = Surface.ROTATION_180; 61 | public static final int RCT_CAMERA_ORIENTATION_LANDSCAPE_LEFT = Surface.ROTATION_90; 62 | public static final int RCT_CAMERA_ORIENTATION_LANDSCAPE_RIGHT = Surface.ROTATION_270; 63 | public static final int RCT_CAMERA_TYPE_FRONT = 1; 64 | public static final int RCT_CAMERA_TYPE_BACK = 2; 65 | public static final int RCT_CAMERA_FLASH_MODE_OFF = 0; 66 | public static final int RCT_CAMERA_FLASH_MODE_ON = 1; 67 | public static final int RCT_CAMERA_FLASH_MODE_AUTO = 2; 68 | public static final int RCT_CAMERA_TORCH_MODE_OFF = 0; 69 | public static final int RCT_CAMERA_TORCH_MODE_ON = 1; 70 | public static final int RCT_CAMERA_TORCH_MODE_AUTO = 2; 71 | public static final String RCT_CAMERA_CAPTURE_QUALITY_PREVIEW = "preview"; 72 | public static final String RCT_CAMERA_CAPTURE_QUALITY_HIGH = "high"; 73 | public static final String RCT_CAMERA_CAPTURE_QUALITY_MEDIUM = "medium"; 74 | public static final String RCT_CAMERA_CAPTURE_QUALITY_LOW = "low"; 75 | public static final String RCT_CAMERA_CAPTURE_QUALITY_1080P = "1080p"; 76 | public static final String RCT_CAMERA_CAPTURE_QUALITY_720P = "720p"; 77 | public static final String RCT_CAMERA_CAPTURE_QUALITY_480P = "480p"; 78 | public static final int MEDIA_TYPE_IMAGE = 1; 79 | public static final int MEDIA_TYPE_VIDEO = 2; 80 | 81 | private static ReactApplicationContext _reactContext; 82 | private RCTSensorOrientationChecker _sensorOrientationChecker; 83 | 84 | private MediaRecorder mMediaRecorder; 85 | private long MRStartTime; 86 | private File mVideoFile; 87 | private Camera mCamera = null; 88 | private Promise mRecordingPromise = null; 89 | private ReadableMap mRecordingOptions; 90 | private Boolean mSafeToCapture = true; 91 | 92 | public RCTZBarCameraModule(ReactApplicationContext reactContext) { 93 | super(reactContext); 94 | _reactContext = reactContext; 95 | _sensorOrientationChecker = new RCTSensorOrientationChecker(_reactContext); 96 | _reactContext.addLifecycleEventListener(this); 97 | } 98 | 99 | public static ReactApplicationContext getReactContextSingleton() { 100 | return _reactContext; 101 | } 102 | 103 | /** 104 | * Callback invoked on new MediaRecorder info. 105 | * 106 | * See https://developer.android.com/reference/android/media/MediaRecorder.OnInfoListener.html 107 | * for more information. 108 | * 109 | * @param mr MediaRecorder instance for which this callback is being invoked. 110 | * @param what Type of info we have received. 111 | * @param extra Extra code, specific to the info type. 112 | */ 113 | public void onInfo(MediaRecorder mr, int what, int extra) { 114 | if ( what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED || 115 | what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) { 116 | if (mRecordingPromise != null) { 117 | releaseMediaRecorder(); // release the MediaRecorder object and resolve promise 118 | } 119 | } 120 | } 121 | 122 | /** 123 | * Callback invoked when a MediaRecorder instance encounters an error while recording. 124 | * 125 | * See https://developer.android.com/reference/android/media/MediaRecorder.OnErrorListener.html 126 | * for more information. 127 | * 128 | * @param mr MediaRecorder instance for which this callback is being invoked. 129 | * @param what Type of error that has occurred. 130 | * @param extra Extra code, specific to the error type. 131 | */ 132 | public void onError(MediaRecorder mr, int what, int extra) { 133 | // On any error, release the MediaRecorder object and resolve promise. In particular, this 134 | // prevents leaving the camera in an unrecoverable state if we crash in the middle of 135 | // recording. 136 | if (mRecordingPromise != null) { 137 | releaseMediaRecorder(); 138 | } 139 | } 140 | 141 | @Override 142 | public String getName() { 143 | return "RCTCameraModule"; 144 | } 145 | 146 | @Nullable 147 | @Override 148 | public Map getConstants() { 149 | return Collections.unmodifiableMap(new HashMap() { 150 | { 151 | put("Aspect", getAspectConstants()); 152 | put("BarCodeType", getBarCodeConstants()); 153 | put("Type", getTypeConstants()); 154 | put("CaptureQuality", getCaptureQualityConstants()); 155 | put("CaptureMode", getCaptureModeConstants()); 156 | put("CaptureTarget", getCaptureTargetConstants()); 157 | put("Orientation", getOrientationConstants()); 158 | put("FlashMode", getFlashModeConstants()); 159 | put("TorchMode", getTorchModeConstants()); 160 | } 161 | 162 | private Map getAspectConstants() { 163 | return Collections.unmodifiableMap(new HashMap() { 164 | { 165 | put("stretch", RCT_CAMERA_ASPECT_STRETCH); 166 | put("fit", RCT_CAMERA_ASPECT_FIT); 167 | put("fill", RCT_CAMERA_ASPECT_FILL); 168 | } 169 | }); 170 | } 171 | 172 | private Map getBarCodeConstants() { 173 | return Collections.unmodifiableMap(new HashMap() { 174 | { 175 | // @TODO add barcode types 176 | } 177 | }); 178 | } 179 | 180 | private Map getTypeConstants() { 181 | return Collections.unmodifiableMap(new HashMap() { 182 | { 183 | put("front", RCT_CAMERA_TYPE_FRONT); 184 | put("back", RCT_CAMERA_TYPE_BACK); 185 | } 186 | }); 187 | } 188 | 189 | private Map getCaptureQualityConstants() { 190 | return Collections.unmodifiableMap(new HashMap() { 191 | { 192 | put("low", RCT_CAMERA_CAPTURE_QUALITY_LOW); 193 | put("medium", RCT_CAMERA_CAPTURE_QUALITY_MEDIUM); 194 | put("high", RCT_CAMERA_CAPTURE_QUALITY_HIGH); 195 | put("photo", RCT_CAMERA_CAPTURE_QUALITY_HIGH); 196 | put("preview", RCT_CAMERA_CAPTURE_QUALITY_PREVIEW); 197 | put("480p", RCT_CAMERA_CAPTURE_QUALITY_480P); 198 | put("720p", RCT_CAMERA_CAPTURE_QUALITY_720P); 199 | put("1080p", RCT_CAMERA_CAPTURE_QUALITY_1080P); 200 | } 201 | }); 202 | } 203 | 204 | private Map getCaptureModeConstants() { 205 | return Collections.unmodifiableMap(new HashMap() { 206 | { 207 | put("still", RCT_CAMERA_CAPTURE_MODE_STILL); 208 | put("video", RCT_CAMERA_CAPTURE_MODE_VIDEO); 209 | } 210 | }); 211 | } 212 | 213 | private Map getCaptureTargetConstants() { 214 | return Collections.unmodifiableMap(new HashMap() { 215 | { 216 | put("memory", RCT_CAMERA_CAPTURE_TARGET_MEMORY); 217 | put("disk", RCT_CAMERA_CAPTURE_TARGET_DISK); 218 | put("cameraRoll", RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL); 219 | put("temp", RCT_CAMERA_CAPTURE_TARGET_TEMP); 220 | } 221 | }); 222 | } 223 | 224 | private Map getOrientationConstants() { 225 | return Collections.unmodifiableMap(new HashMap() { 226 | { 227 | put("auto", RCT_CAMERA_ORIENTATION_AUTO); 228 | put("landscapeLeft", RCT_CAMERA_ORIENTATION_LANDSCAPE_LEFT); 229 | put("landscapeRight", RCT_CAMERA_ORIENTATION_LANDSCAPE_RIGHT); 230 | put("portrait", RCT_CAMERA_ORIENTATION_PORTRAIT); 231 | put("portraitUpsideDown", RCT_CAMERA_ORIENTATION_PORTRAIT_UPSIDE_DOWN); 232 | } 233 | }); 234 | } 235 | 236 | private Map getFlashModeConstants() { 237 | return Collections.unmodifiableMap(new HashMap() { 238 | { 239 | put("off", RCT_CAMERA_FLASH_MODE_OFF); 240 | put("on", RCT_CAMERA_FLASH_MODE_ON); 241 | put("auto", RCT_CAMERA_FLASH_MODE_AUTO); 242 | } 243 | }); 244 | } 245 | 246 | private Map getTorchModeConstants() { 247 | return Collections.unmodifiableMap(new HashMap() { 248 | { 249 | put("off", RCT_CAMERA_TORCH_MODE_OFF); 250 | put("on", RCT_CAMERA_TORCH_MODE_ON); 251 | put("auto", RCT_CAMERA_TORCH_MODE_AUTO); 252 | } 253 | }); 254 | } 255 | }); 256 | } 257 | 258 | /** 259 | * Prepare media recorder for video capture. 260 | * 261 | * See "Capturing Videos" at https://developer.android.com/guide/topics/media/camera.html for 262 | * a guideline of steps and more information in general. 263 | * 264 | * @param options Options. 265 | * @return Throwable; null if no errors. 266 | */ 267 | private Throwable prepareMediaRecorder(ReadableMap options) { 268 | // Prepare CamcorderProfile instance, setting essential options. 269 | CamcorderProfile cm = RCTCamera.getInstance().setCaptureVideoQuality(options.getInt("type"), options.getString("quality")); 270 | if (cm == null) { 271 | return new RuntimeException("CamcorderProfile not found in prepareMediaRecorder."); 272 | } 273 | 274 | // Unlock camera to make available for MediaRecorder. Note that this statement must be 275 | // executed before calling setCamera when configuring the MediaRecorder instance. 276 | mCamera.unlock(); 277 | 278 | // Create new MediaRecorder instance. 279 | mMediaRecorder = new MediaRecorder(); 280 | 281 | // Attach callback to handle maxDuration (@see onInfo method in this file). 282 | mMediaRecorder.setOnInfoListener(this); 283 | // Attach error listener (@see onError method in this file). 284 | mMediaRecorder.setOnErrorListener(this); 285 | 286 | // Set camera. 287 | mMediaRecorder.setCamera(mCamera); 288 | 289 | // Set AV sources. 290 | mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); 291 | mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA); 292 | 293 | // Adjust for orientation. 294 | mMediaRecorder.setOrientationHint(RCTCamera.getInstance().getAdjustedDeviceOrientation()); 295 | 296 | // Set video output format and encoding using CamcorderProfile. 297 | cm.fileFormat = MediaRecorder.OutputFormat.MPEG_4; 298 | mMediaRecorder.setProfile(cm); 299 | 300 | // Set video output file. 301 | mVideoFile = null; 302 | switch (options.getInt("target")) { 303 | case RCT_CAMERA_CAPTURE_TARGET_MEMORY: 304 | mVideoFile = getTempMediaFile(MEDIA_TYPE_VIDEO); // temporarily 305 | break; 306 | case RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL: 307 | mVideoFile = getOutputCameraRollFile(MEDIA_TYPE_VIDEO); 308 | break; 309 | case RCT_CAMERA_CAPTURE_TARGET_TEMP: 310 | mVideoFile = getTempMediaFile(MEDIA_TYPE_VIDEO); 311 | break; 312 | default: 313 | case RCT_CAMERA_CAPTURE_TARGET_DISK: 314 | mVideoFile = getOutputMediaFile(MEDIA_TYPE_VIDEO); 315 | break; 316 | } 317 | if (mVideoFile == null) { 318 | return new RuntimeException("Error while preparing output file in prepareMediaRecorder."); 319 | } 320 | mMediaRecorder.setOutputFile(mVideoFile.getPath()); 321 | 322 | if (options.hasKey("totalSeconds")) { 323 | int totalSeconds = options.getInt("totalSeconds"); 324 | mMediaRecorder.setMaxDuration(totalSeconds * 1000); 325 | } 326 | 327 | if (options.hasKey("maxFileSize")) { 328 | int maxFileSize = options.getInt("maxFileSize"); 329 | mMediaRecorder.setMaxFileSize(maxFileSize); 330 | } 331 | 332 | // Prepare the MediaRecorder instance with the provided configuration settings. 333 | try { 334 | mMediaRecorder.prepare(); 335 | } catch (Exception ex) { 336 | Log.e(TAG, "Media recorder prepare error.", ex); 337 | releaseMediaRecorder(); 338 | return ex; 339 | } 340 | 341 | return null; 342 | } 343 | 344 | private void record(final ReadableMap options, final Promise promise) { 345 | if (mRecordingPromise != null) { 346 | return; 347 | } 348 | 349 | mCamera = RCTCamera.getInstance().acquireCameraInstance(options.getInt("type")); 350 | if (mCamera == null) { 351 | promise.reject(new RuntimeException("No camera found.")); 352 | return; 353 | } 354 | 355 | Throwable prepareError = prepareMediaRecorder(options); 356 | if (prepareError != null) { 357 | promise.reject(prepareError); 358 | return; 359 | } 360 | 361 | try { 362 | mMediaRecorder.start(); 363 | MRStartTime = System.currentTimeMillis(); 364 | mRecordingOptions = options; 365 | mRecordingPromise = promise; // only got here if mediaRecorder started 366 | } catch (Exception ex) { 367 | Log.e(TAG, "Media recorder start error.", ex); 368 | promise.reject(ex); 369 | } 370 | } 371 | 372 | /** 373 | * Release media recorder following video capture (or failure to start recording session). 374 | * 375 | * See "Capturing Videos" at https://developer.android.com/guide/topics/media/camera.html for 376 | * a guideline of steps and more information in general. 377 | */ 378 | private void releaseMediaRecorder() { 379 | // Must record at least a second or MediaRecorder throws exceptions on some platforms 380 | long duration = System.currentTimeMillis() - MRStartTime; 381 | if (duration < 1500) { 382 | try { 383 | Thread.sleep(1500 - duration); 384 | } catch(InterruptedException ex) { 385 | Log.e(TAG, "releaseMediaRecorder thread sleep error.", ex); 386 | } 387 | } 388 | 389 | // Release actual MediaRecorder instance. 390 | if (mMediaRecorder != null) { 391 | // Stop recording video. 392 | try { 393 | mMediaRecorder.stop(); // stop the recording 394 | } catch (RuntimeException ex) { 395 | Log.e(TAG, "Media recorder stop error.", ex); 396 | } 397 | 398 | // Optionally, remove the configuration settings from the recorder. 399 | mMediaRecorder.reset(); 400 | 401 | // Release the MediaRecorder. 402 | mMediaRecorder.release(); 403 | 404 | // Reset variable. 405 | mMediaRecorder = null; 406 | } 407 | 408 | // Lock the camera so that future MediaRecorder sessions can use it by calling 409 | // Camera.lock(). Note this is not required on Android 4.0+ unless the 410 | // MediaRecorder.prepare() call fails. 411 | if (mCamera != null) { 412 | mCamera.lock(); 413 | } 414 | 415 | if (mRecordingPromise == null) { 416 | return; 417 | } 418 | 419 | File f = new File(mVideoFile.getPath()); 420 | if (!f.exists()) { 421 | mRecordingPromise.reject(new RuntimeException("There is nothing recorded.")); 422 | mRecordingPromise = null; 423 | return; 424 | } 425 | 426 | f.setReadable(true, false); // so mediaplayer can play it 427 | f.setWritable(true, false); // so can clean it up 428 | 429 | WritableMap response = new WritableNativeMap(); 430 | switch (mRecordingOptions.getInt("target")) { 431 | case RCT_CAMERA_CAPTURE_TARGET_MEMORY: 432 | byte[] encoded = convertFileToByteArray(mVideoFile); 433 | response.putString("data", new String(encoded, Base64.DEFAULT)); 434 | mRecordingPromise.resolve(response); 435 | f.delete(); 436 | break; 437 | case RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL: 438 | ContentValues values = new ContentValues(); 439 | values.put(MediaStore.Video.Media.DATA, mVideoFile.getPath()); 440 | values.put(MediaStore.Video.Media.TITLE, mRecordingOptions.hasKey("title") ? mRecordingOptions.getString("title") : "video"); 441 | 442 | if (mRecordingOptions.hasKey("description")) { 443 | values.put(MediaStore.Video.Media.DESCRIPTION, mRecordingOptions.hasKey("description")); 444 | } 445 | 446 | if (mRecordingOptions.hasKey("latitude")) { 447 | values.put(MediaStore.Video.Media.LATITUDE, mRecordingOptions.getString("latitude")); 448 | } 449 | 450 | if (mRecordingOptions.hasKey("longitude")) { 451 | values.put(MediaStore.Video.Media.LONGITUDE, mRecordingOptions.getString("longitude")); 452 | } 453 | 454 | values.put(MediaStore.Video.Media.MIME_TYPE, "video/mp4"); 455 | _reactContext.getContentResolver().insert(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, values); 456 | addToMediaStore(mVideoFile.getAbsolutePath()); 457 | response.putString("path", Uri.fromFile(mVideoFile).toString()); 458 | mRecordingPromise.resolve(response); 459 | break; 460 | case RCT_CAMERA_CAPTURE_TARGET_TEMP: 461 | case RCT_CAMERA_CAPTURE_TARGET_DISK: 462 | response.putString("path", Uri.fromFile(mVideoFile).toString()); 463 | mRecordingPromise.resolve(response); 464 | } 465 | 466 | mRecordingPromise = null; 467 | } 468 | 469 | public static byte[] convertFileToByteArray(File f) 470 | { 471 | byte[] byteArray = null; 472 | try 473 | { 474 | InputStream inputStream = new FileInputStream(f); 475 | ByteArrayOutputStream bos = new ByteArrayOutputStream(); 476 | byte[] b = new byte[1024*8]; 477 | int bytesRead; 478 | 479 | while ((bytesRead = inputStream.read(b)) != -1) { 480 | bos.write(b, 0, bytesRead); 481 | } 482 | 483 | byteArray = bos.toByteArray(); 484 | } 485 | catch (IOException e) 486 | { 487 | e.printStackTrace(); 488 | } 489 | return byteArray; 490 | } 491 | 492 | @ReactMethod 493 | public void capture(final ReadableMap options, final Promise promise) { 494 | int orientation = options.hasKey("orientation") ? options.getInt("orientation") : RCTCamera.getInstance().getOrientation(); 495 | if (orientation == RCT_CAMERA_ORIENTATION_AUTO) { 496 | _sensorOrientationChecker.onResume(); 497 | _sensorOrientationChecker.registerOrientationListener(new RCTSensorOrientationListener() { 498 | @Override 499 | public void orientationEvent() { 500 | int deviceOrientation = _sensorOrientationChecker.getOrientation(); 501 | _sensorOrientationChecker.unregisterOrientationListener(); 502 | _sensorOrientationChecker.onPause(); 503 | captureWithOrientation(options, promise, deviceOrientation); 504 | } 505 | }); 506 | } else { 507 | captureWithOrientation(options, promise, orientation); 508 | } 509 | } 510 | 511 | private void captureWithOrientation(final ReadableMap options, final Promise promise, int deviceOrientation) { 512 | Camera camera = RCTCamera.getInstance().acquireCameraInstance(options.getInt("type")); 513 | if (null == camera) { 514 | promise.reject("No camera found."); 515 | return; 516 | } 517 | 518 | if (options.getInt("mode") == RCT_CAMERA_CAPTURE_MODE_VIDEO) { 519 | record(options, promise); 520 | return; 521 | } 522 | 523 | RCTCamera.getInstance().setCaptureQuality(options.getInt("type"), options.getString("quality")); 524 | 525 | if (options.hasKey("playSoundOnCapture") && options.getBoolean("playSoundOnCapture")) { 526 | MediaActionSound sound = new MediaActionSound(); 527 | sound.play(MediaActionSound.SHUTTER_CLICK); 528 | } 529 | 530 | if (options.hasKey("quality")) { 531 | RCTCamera.getInstance().setCaptureQuality(options.getInt("type"), options.getString("quality")); 532 | } 533 | 534 | RCTCamera.getInstance().adjustCameraRotationToDeviceOrientation(options.getInt("type"), deviceOrientation); 535 | camera.setPreviewCallback(null); 536 | 537 | Camera.PictureCallback captureCallback = new Camera.PictureCallback() { 538 | @Override 539 | public void onPictureTaken(final byte[] data, Camera camera) { 540 | camera.stopPreview(); 541 | camera.startPreview(); 542 | 543 | AsyncTask.execute(new Runnable() { 544 | @Override 545 | public void run() { 546 | processImage(new MutableImage(data), options, promise); 547 | } 548 | }); 549 | 550 | mSafeToCapture = true; 551 | } 552 | }; 553 | 554 | if(mSafeToCapture) { 555 | try { 556 | camera.takePicture(null, null, captureCallback); 557 | mSafeToCapture = false; 558 | } catch(RuntimeException ex) { 559 | Log.e(TAG, "Couldn't capture photo.", ex); 560 | } 561 | } 562 | } 563 | 564 | /** 565 | * synchronized in order to prevent the user crashing the app by taking many photos and them all being processed 566 | * concurrently which would blow the memory (esp on smaller devices), and slow things down. 567 | */ 568 | private synchronized void processImage(MutableImage mutableImage, ReadableMap options, Promise promise) { 569 | boolean shouldFixOrientation = options.hasKey("fixOrientation") && options.getBoolean("fixOrientation"); 570 | if(shouldFixOrientation) { 571 | try { 572 | mutableImage.fixOrientation(); 573 | } catch (MutableImage.ImageMutationFailedException e) { 574 | promise.reject("Error fixing orientation image", e); 575 | } 576 | } 577 | 578 | boolean shouldMirror = options.hasKey("mirrorImage") && options.getBoolean("mirrorImage"); 579 | if (shouldMirror) { 580 | try { 581 | mutableImage.mirrorImage(); 582 | } catch (MutableImage.ImageMutationFailedException e) { 583 | promise.reject("Error mirroring image", e); 584 | } 585 | } 586 | 587 | int jpegQualityPercent = 80; 588 | if(options.hasKey("jpegQuality")) { 589 | jpegQualityPercent = options.getInt("jpegQuality"); 590 | } 591 | 592 | switch (options.getInt("target")) { 593 | case RCT_CAMERA_CAPTURE_TARGET_MEMORY: 594 | String encoded = mutableImage.toBase64(jpegQualityPercent); 595 | WritableMap response = new WritableNativeMap(); 596 | response.putString("data", encoded); 597 | promise.resolve(response); 598 | break; 599 | case RCT_CAMERA_CAPTURE_TARGET_CAMERA_ROLL: { 600 | File cameraRollFile = getOutputCameraRollFile(MEDIA_TYPE_IMAGE); 601 | if (cameraRollFile == null) { 602 | promise.reject("Error creating media file."); 603 | return; 604 | } 605 | 606 | try { 607 | mutableImage.writeDataToFile(cameraRollFile, options, jpegQualityPercent); 608 | } catch (IOException | NullPointerException e) { 609 | promise.reject("failed to save image file", e); 610 | return; 611 | } 612 | 613 | addToMediaStore(cameraRollFile.getAbsolutePath()); 614 | 615 | resolveImage(cameraRollFile, promise, true); 616 | 617 | break; 618 | } 619 | case RCT_CAMERA_CAPTURE_TARGET_DISK: { 620 | File pictureFile = getOutputMediaFile(MEDIA_TYPE_IMAGE); 621 | if (pictureFile == null) { 622 | promise.reject("Error creating media file."); 623 | return; 624 | } 625 | 626 | try { 627 | mutableImage.writeDataToFile(pictureFile, options, 85); 628 | } catch (IOException e) { 629 | promise.reject("failed to save image file", e); 630 | return; 631 | } 632 | 633 | resolveImage(pictureFile, promise, false); 634 | 635 | break; 636 | } 637 | case RCT_CAMERA_CAPTURE_TARGET_TEMP: { 638 | File tempFile = getTempMediaFile(MEDIA_TYPE_IMAGE); 639 | if (tempFile == null) { 640 | promise.reject("Error creating media file."); 641 | return; 642 | } 643 | 644 | try { 645 | mutableImage.writeDataToFile(tempFile, options, 85); 646 | } catch (IOException e) { 647 | promise.reject("failed to save image file", e); 648 | return; 649 | } 650 | 651 | resolveImage(tempFile, promise, false); 652 | 653 | break; 654 | } 655 | } 656 | } 657 | 658 | @ReactMethod 659 | public void stopCapture(final Promise promise) { 660 | if (mRecordingPromise != null) { 661 | releaseMediaRecorder(); // release the MediaRecorder object 662 | promise.resolve("Finished recording."); 663 | } else { 664 | promise.resolve("Not recording."); 665 | } 666 | } 667 | 668 | @ReactMethod 669 | public void hasFlash(ReadableMap options, final Promise promise) { 670 | Camera camera = RCTCamera.getInstance().acquireCameraInstance(options.getInt("type")); 671 | if (null == camera) { 672 | promise.reject("No camera found."); 673 | return; 674 | } 675 | List flashModes = camera.getParameters().getSupportedFlashModes(); 676 | promise.resolve(null != flashModes && !flashModes.isEmpty()); 677 | } 678 | 679 | private File getOutputMediaFile(int type) { 680 | // Get environment directory type id from requested media type. 681 | String environmentDirectoryType; 682 | if (type == MEDIA_TYPE_IMAGE) { 683 | environmentDirectoryType = Environment.DIRECTORY_PICTURES; 684 | } else if (type == MEDIA_TYPE_VIDEO) { 685 | environmentDirectoryType = Environment.DIRECTORY_MOVIES; 686 | } else { 687 | Log.e(TAG, "Unsupported media type:" + type); 688 | return null; 689 | } 690 | 691 | return getOutputFile( 692 | type, 693 | Environment.getExternalStoragePublicDirectory(environmentDirectoryType) 694 | ); 695 | } 696 | 697 | private File getOutputCameraRollFile(int type) { 698 | return getOutputFile( 699 | type, 700 | Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM) 701 | ); 702 | } 703 | 704 | private File getOutputFile(int type, File storageDir) { 705 | // Create the storage directory if it does not exist 706 | if (!storageDir.exists()) { 707 | if (!storageDir.mkdirs()) { 708 | Log.e(TAG, "failed to create directory:" + storageDir.getAbsolutePath()); 709 | return null; 710 | } 711 | } 712 | 713 | // Create a media file name 714 | String fileName = String.format("%s", new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date())); 715 | 716 | if (type == MEDIA_TYPE_IMAGE) { 717 | fileName = String.format("IMG_%s.jpg", fileName); 718 | } else if (type == MEDIA_TYPE_VIDEO) { 719 | fileName = String.format("VID_%s.mp4", fileName); 720 | } else { 721 | Log.e(TAG, "Unsupported media type:" + type); 722 | return null; 723 | } 724 | 725 | return new File(String.format("%s%s%s", storageDir.getPath(), File.separator, fileName)); 726 | } 727 | 728 | private File getTempMediaFile(int type) { 729 | try { 730 | String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); 731 | File outputDir = _reactContext.getCacheDir(); 732 | File outputFile; 733 | 734 | if (type == MEDIA_TYPE_IMAGE) { 735 | outputFile = File.createTempFile("IMG_" + timeStamp, ".jpg", outputDir); 736 | } else if (type == MEDIA_TYPE_VIDEO) { 737 | outputFile = File.createTempFile("VID_" + timeStamp, ".mp4", outputDir); 738 | } else { 739 | Log.e(TAG, "Unsupported media type:" + type); 740 | return null; 741 | } 742 | return outputFile; 743 | } catch (Exception e) { 744 | Log.e(TAG, e.getMessage()); 745 | return null; 746 | } 747 | } 748 | 749 | private void addToMediaStore(String path) { 750 | MediaScannerConnection.scanFile(_reactContext, new String[] { path }, null, null); 751 | } 752 | 753 | /** 754 | * LifecycleEventListener overrides 755 | */ 756 | @Override 757 | public void onHostResume() { 758 | // ... do nothing 759 | } 760 | 761 | @Override 762 | public void onHostPause() { 763 | // On pause, we stop any pending recording session 764 | if (mRecordingPromise != null) { 765 | releaseMediaRecorder(); 766 | } 767 | } 768 | 769 | @Override 770 | public void onHostDestroy() { 771 | // ... do nothing 772 | } 773 | 774 | private void resolveImage(final File imageFile, final Promise promise, boolean addToMediaStore) { 775 | final WritableMap response = new WritableNativeMap(); 776 | response.putString("path", Uri.fromFile(imageFile).toString()); 777 | 778 | if(addToMediaStore) { 779 | // borrowed from react-native CameraRollManager, it finds and returns the 'internal' 780 | // representation of the image uri that was just saved. 781 | // e.g. content://media/external/images/media/123 782 | MediaScannerConnection.scanFile( 783 | _reactContext, 784 | new String[]{imageFile.getAbsolutePath()}, 785 | null, 786 | new MediaScannerConnection.OnScanCompletedListener() { 787 | @Override 788 | public void onScanCompleted(String path, Uri uri) { 789 | if (uri != null) { 790 | response.putString("mediaUri", uri.toString()); 791 | } 792 | 793 | promise.resolve(response); 794 | } 795 | }); 796 | } else { 797 | promise.resolve(response); 798 | } 799 | } 800 | 801 | } 802 | -------------------------------------------------------------------------------- /android/src/main/java/com/barcodescanner/RCTZBarCameraPackage.java: -------------------------------------------------------------------------------- 1 | package com.barcodescanner; 2 | 3 | import com.facebook.react.ReactPackage; 4 | import com.facebook.react.bridge.JavaScriptModule; 5 | import com.facebook.react.bridge.NativeModule; 6 | import com.facebook.react.bridge.ReactApplicationContext; 7 | import com.facebook.react.uimanager.ViewManager; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | public class RCTZBarCameraPackage implements ReactPackage { 13 | 14 | @Override 15 | public List createNativeModules(ReactApplicationContext reactApplicationContext) { 16 | return Collections.singletonList(new RCTZBarCameraModule(reactApplicationContext)); 17 | } 18 | 19 | @Override 20 | public List createViewManagers(ReactApplicationContext reactApplicationContext) { 21 | //noinspection ArraysAsListWithZeroOrOneArgument 22 | return Collections.singletonList(new RCTCameraViewManager()); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-android-barcodescanner", 3 | "version": "0.1.8", 4 | "description": "android扫码插件", 5 | "main": "ZBarQRScannerRectView.js", 6 | "scripts": { 7 | "test": "npm test" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/react-native-studio/react-native-android-barcodescanner.git" 12 | }, 13 | "keywords": [ 14 | "scan", 15 | "qrcode", 16 | "barcode" 17 | ], 18 | "author": "lmy2534290808", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/react-native-studio/react-native-android-barcodescanner/issues" 22 | }, 23 | "homepage": "https://github.com/react-native-stutio/react-native-android-barcodescanner#readme" 24 | } 25 | --------------------------------------------------------------------------------