├── .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 [](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 | 
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 |
--------------------------------------------------------------------------------