深圳幻海软件技术有限公司 欢迎您!

记录--uniapp自定义相机 自定义界面拍照录像闪光灯切换摄像头

2023-03-02

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助因公司业务需要,需要开发水印相机功能,而项目代码用的uniapp框架,App端只能简单调用系统的相机,无法自定义界面,在此基础上,只能开发自定义插件来完成功能(自定义原生插件,即是用原生代码来编写组件实现功能,然后供uniapp项目调用),

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

因公司业务需要,需要开发水印相机功能,而项目代码用的uniapp框架,App端只能简单调用系统的相机,无法自定义界面,在此基础上,只能开发自定义插件来完成功能(自定义原生插件,即是用原生代码来编写组件实现功能,然后供uniapp项目调用),经过半个月的研究和开发,完成了这款插件,以高度自由的形式提供了开发者相机自定义界面的需求,只需要在相机界面引入
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
            <!-- 相机原生插件 START -->
 
            <camera-view
 
                ref="cameraObj"
 
                class="camera_view"
 
                :defaultCamera="currentCamera"
 
                @receiveRatio="receiveRatio"
 
                @takePhotoSuccess="takePhotoSuccess"
 
                @takePhotoFail="takePhotoFail"
 
                @recordSuccess="recordSuccess"
 
                @recordFail="recordFail"
 
                @receiveInfo="onError"
 
                :style="'width:'+previewWidth+'px;height:'+previewHeight+'px;margin-left:-'+marginLeft+'px'"
 
                >
 
            </camera-view>
 
            <!-- 相机原生插件 END -->

这里建议宽高设置为全屏,然后在界面上自定义叠加自己的按钮文字等实现自己的界面功能,然后调用插件提供的api实现物理功能

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
// 拍照
 
takePhoto(){
 
    console.error("开始拍照")
 
    // 设置水印
 
    this.$refs.cameraObj.addWaterText({
 
        "date":this.tempDateStr || "",
 
        "logo":"·七彩云·|水印相机",
 
        "address":(this.showAddress ? this.address:""),
 
        "time":this.tempTimeStr || "",
 
        "week":this.weekDay || "",
 
        "remark":(this.showRemark ? this.remark:"")
 
    });
 
    // 调用拍照api
 
    this.$refs.cameraObj.takePhoto();
 
},
 
// 切换闪光灯
 
    switchFlash(){
 
        if(this.flashStatus === 0){
 
            this.flashStatus = 1;
 
            this.$refs.cameraObj.openFlash();
 
        }else{
 
            this.flashStatus = 0;
 
            this.$refs.cameraObj.closeFlash();
 
        }
 
    },
 
// 切换摄像头
 
switchCamera(){
 
    if(this.currentCamera === "0"){
 
        this.currentCamera = "1";
 
        this.$refs.cameraObj.openFront();
 
    }else{
 
        this.currentCamera = "0";
 
        this.$refs.cameraObj.openBack();
 
    }
 
},    

原生插件开发文档

Android / IOS 原生插件都有两种类型扩展

1、  Module 扩展 非 UI 的特定功能. ( 直白点说就是只注重功能 )

2、 Component 扩展 实现特别功能的 Native 控件. ( 侧重点在界面 )

比如我们想实现一个自定义的原生按钮,那就得扩展Component,因为需要有界面,而想实现一个提供各种api的插件,比如加减乘除算法等不需要界面显示,只有结果数据的,这种就可以用Module

附上链接: 前往下载插件和demo实例

一、Android原生插件的实现

首先android类继承uniapp的特殊类UniComponent

?
1
public class LuanQingCamera extends UniComponent<FrameLayout>

在initComponentHostView这个固定方法返回一个组件

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
@Override
 
    protected FrameLayout initComponentHostView(Context context) {
 
        // 我们自定义了一个FrameLayout的组件(为了方便后面扩展水印)
 
        FrameLayout frameLayout = new FrameLayout(context);
 
        // 创建一个SurfaceView用来承载摄像头预览
 
        SurfaceView surfaceView = new SurfaceView(context);
 
        // 添加到布局中
 
        frameLayout.addView(surfaceView);
 
         
 
        if (mHolder == null) {
 
            mHolder = surfaceView.getHolder();
 
 
 
 
            mHolder.addCallback(new SurfaceHolder.Callback() {
 
                @Override
 
                public void surfaceCreated(SurfaceHolder holder) {
 
                    // 检查权限 如果权限满足就将打开摄像头,初始化预览
 
                    checkPermission();
 
                }
 
 
 
 
                @Override
 
                public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
 
 
 
 
                }
 
 
 
 
                @Override
 
                public void surfaceDestroyed(SurfaceHolder holder) {
 
 
 
 
                }
 
            });
 
        }
 
 
 
 
        return frameLayout;
 
    }

申请权限,android 6.0起需一些危险权限要动态申请,因此我们在使用摄像头前申请

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
    @UniJSMethod
 
    public void checkPermission() {
 
        Context mContent = mUniSDKInstance.getContext();
 
        if(mContent instanceof Activity){
 
 
 
 
            // 用于请求权限的列表
 
            List<String> permissions = new ArrayList<>();
 
            // 判断权限是否足够的标识变量
 
            boolean isEnoughPermission = true;
 
 
 
 
            // 权限检查和判断模块 START
 
            List<PermissionEntity> checkList = new ArrayList<>();
 
            checkList.add(new PermissionEntity(Manifest.permission.CAMERA,"摄像头相机权限"));
 
            checkList.add(new PermissionEntity(Manifest.permission.RECORD_AUDIO,"录音录制权限"));
 
            checkList.add(new PermissionEntity(Manifest.permission.WRITE_EXTERNAL_STORAGE,"文件读写权限"));
 
 
 
 
            for (PermissionEntity p : checkList){
 
                // 判断是否有权限
 
                boolean isHas = ActivityCompat.checkSelfPermission(mUniSDKInstance.getContext(), p.getPermissionName()) == PackageManager.PERMISSION_GRANTED;
 
                if (isHas) {
 
                    // 已经有权限(可能用户在设置中开启了)的话就把配置中的权限状态设置为已有权限
 
                    SharedData.setParam(mUniSDKInstance.getContext(),p.getPermissionName(),1);
 
                }
 
 
 
 
                // 权限状态: 0|无权限  1|有权限  2|已拒绝
 
                int status = (int) SharedData.getParam(mUniSDKInstance.getContext(),p.getPermissionName(),0);
 
                if(status == 0){
 
                    // 添加到权限请求列表
 
                    permissions.add(p.getPermissionName());
 
                    isEnoughPermission = false;
 
                }else if(status == 2){
 
                    isEnoughPermission = false;
 
                    backData("receiveInfo", 2003 ,"缺少"+p.getDescribe());
 
                }
 
            }
 
 
 
 
            // 如果权限足够了直接初始化相机
 
            if(isEnoughPermission){
 
                initCameraOption();
 
                return;
 
            }
 
            // 权限检查和判断模块 START
 
 
 
 
            if(permissions.size() > 0){
 
                EsayPermissions.with((Activity) mContent).permission(permissions).request(new OnPermission() {
 
                    @Override
 
                    public void hasPermission(List<String> granted, boolean isAll) {
 
                        if(isAll){
 
                            initCameraOption();
 
                        }else{
 
                            backData("receiveInfo", 2003 ,"缺少摄像头|录制录音|文件读写权限");
 
                        }
 
                    }
 
 
 
 
                    @Override
 
                    public void noPermission(List<String> denied, boolean quick) {
 
                        // 把已拒绝的权限记录,下次不再弹出权限申请,因为不这样做存在会被应用市场拒绝并下架的风险
 
                        for (String permission : denied){
 
                            // 用户拒绝
 
                            SharedData.setParam(mUniSDKInstance.getContext(),permission,2);
 
                        }
 
                        backData("receiveInfo", 2003 ,"未授予摄像头|录制录音|文件读写权限");
 
                    }
 
                });
 
            }
 
        }
 
    }

摄像头开始预览,显示可见的内容

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
// 开始预览
 
    @UniJSMethod
 
    public void startPreview() {
 
        try {
 
            if(mCameraCaptureSession != null){
 
                mCameraCaptureSession.stopRepeating();//停止之前的会话操作,准备切换到预览画面
 
                mCameraCaptureSession.close();//关闭之前的会话
 
                mCameraCaptureSession = null;
 
            }
 
 
 
            //创建预览请求
 
            mPreviewCaptureRequestBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
 
            // 设置自动对焦模式
 
            mPreviewCaptureRequestBuilder.set(CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
 
 
 
 
            //设置Surface作为预览数据的显示界面
 
            mPreviewCaptureRequestBuilder.addTarget(mHolder.getSurface());
 
            //创建相机捕获会话,第一个参数是捕获数据的输出Surface列表,第二个参数是CameraCaptureSession的状态回调接口,当它创建好后会回调onConfigured方法,第三个参数用来确定Callback在哪个线程执行,为null的话就在当前线程执行
 
            mCameraDevice.createCaptureSession(Arrays.asList(mHolder.getSurface(),mImageReader.getSurface()),new CameraCaptureSession.StateCallback() {
 
                @Override
 
                public void onConfigured(CameraCaptureSession session) {
 
                    mCameraCaptureSession = session;
 
                    try {
 
                        //开始预览
 
                        mPreviewCaptureRequest = mPreviewCaptureRequestBuilder.build();
 
                        UniLogUtils.e("初始化开启预览");
 
                        //设置反复捕获数据的请求,这样预览界面就会一直有数据显示
 
                        mCameraCaptureSession.setRepeatingRequest(mPreviewCaptureRequest, null, null);
 
                    } catch (CameraAccessException e) {
 
                        e.printStackTrace();
 
                    }
 
                }
 
 
 
 
                @Override
 
                public void onConfigureFailed(CameraCaptureSession session) {
 
                    UniLogUtils.e("预览失败");
 
                }
 
            }, null);
 
        } catch (CameraAccessException e) {
 
            e.printStackTrace();
 
        }
 
    }

执行拍照功能

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
@UniJSMethod
 
    public void takePhoto() {
 
        UniLogUtils.e("准备开始拍照");
 
        if (mCameraDevice == null) return;
 
        try {
 
            imageFileName = System.currentTimeMillis() + ".jpg";
 
 
 
 
            //首先我们创建请求拍照的CaptureRequest
 
            CaptureRequest.Builder captureBuilder = mCameraDevice.createCaptureRequest(CameraDevice.TEMPLATE_STILL_CAPTURE);
 
 
 
 
            Context context = mUniSDKInstance.getContext();
 
            if(context instanceof Activity){
 
                Activity activity = (Activity)mUniSDKInstance.getContext();
 
                //获取屏幕方向
 
                int rotation = activity.getWindowManager().getDefaultDisplay().getRotation();
 
                //一个 CaptureRequest 除了需要配置很多参数之外,还要求至少配置一个 Surface(任何相机操作的本质都是为了捕获图像),
 
                captureBuilder.addTarget(mImageReader.getSurface());
 
 
 
 
                // 自动对焦
 
//                captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE);
 
//              // 自动曝光开
 
                captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON);
 
\
 
 
//                captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_OFF);
 
\
 
 
                // 这里有个坑,设置闪光灯必须先设置曝光
 
                if(flashState == 0){
 
                    captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_OFF);
 
                }else{
 
                    captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE);
 
                }
 
 
 
 
//                captureBuilder.set(CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH);
 
//                captureBuilder.set(CaptureRequest.FLASH_MODE, CaptureRequest.FLASH_MODE_SINGLE);
 
                captureBuilder.set(CaptureRequest.JPEG_ORIENTATION, ORIENTATIONS.get(rotation));
 
 
 
 
                mCameraCaptureSession.stopRepeating();
 
                CameraCaptureSession.CaptureCallback captureCallback = new CameraCaptureSession.CaptureCallback() {
 
                    @Override
 
                    public void onCaptureCompleted(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull TotalCaptureResult result) {
 
                        super.onCaptureCompleted(session, request, result);
 
                        UniLogUtils.e("拍照成功:");
 
                        backData("takePhotoSuccess", 200 ,"ok");
 
                        startPreview();
 
                    }
 
 
 
 
                    @Override
 
                    public void onCaptureFailed(@NonNull CameraCaptureSession session, @NonNull CaptureRequest request, @NonNull CaptureFailure failure) {
 
                        super.onCaptureFailed(session, request, failure);
 
                        UniLogUtils.e("拍照失败:");
 
                        backData("takePhotoFail", 2001 ,"拍照操作失败");
 
                    }
 
                };
 
                UniLogUtils.e("开始拍照");
 
 
 
 
                mCameraCaptureSession.capture(captureBuilder.build(), captureCallback, null);
 
            }
 
        } catch (CameraAccessException e) {
 
            e.printStackTrace();
 
        }
 
    }

二、IOS原生插件的实现

ios端相比较,更为简单

头部文件 .h

?
1
2
3
4
5
6
7
8
9
10
11
#import <AVFoundation/AVFoundation.h>
 
#import "DCUniComponent.h"
 
NS_ASSUME_NONNULL_BEGIN
 
@interface LQCamera : DCUniComponent
 
@end
 
NS_ASSUME_NONNULL_END

.m文件实现固定函数,并返回一个组件

?
1
2
3
4
5
6
7
- (UIView *)loadView {
 
    NSLog(@"插件日志:loadView");
 
    return [UIView new];
 
}

初始化一些摄像头参数

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
- (void)viewDidLoad {
 
    NSLog(@"插件日志:viewDidLoad");
 
     
 
    self.session = [[AVCaptureSession alloc] init];
 
     
 
    //创建一个AVCaptureMovieFileOutput 实例,用于将Quick Time 电影录制到文件系统
 
    self.movieOutput = [[AVCaptureMovieFileOutput alloc]init];
 
    //输出连接 判断是否可用,可用则添加到输出连接中去
 
    if ([self.session canAddOutput:self.movieOutput])
 
    {
 
        [self.session addOutput:self.movieOutput];
 
    }
 
     
 
    //     拿到的图像的大小可以自行设定
 
    //    AVCaptureSessionPresetHigh
 
    //    AVCaptureSessionPreset320x240
 
    //    AVCaptureSessionPreset352x288
 
    //    AVCaptureSessionPreset640x480
 
    //    AVCaptureSessionPreset960x540
 
    //    AVCaptureSessionPreset1280x720
 
    //    AVCaptureSessionPreset1920x1080
 
    //    AVCaptureSessionPreset3840x2160
 
    self.session.sessionPreset = AVCaptureSessionPreset1920x1080;
 
     
 
    //AVCaptureStillImageOutput 实例 从摄像头捕捉静态图片
 
    self.imageOutput = [[AVCaptureStillImageOutput alloc]init];
 
    //配置字典:希望捕捉到JPEG格式的图片
 
    self.imageOutput.outputSettings = @{AVVideoCodecKey:AVVideoCodecJPEG};
 
    if ([self.session canAddOutput:self.imageOutput]) {
 
        [self.session addOutput:self.imageOutput];
 
    }
 
\
 
 
    self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
 
    NSError * error = nil;
 
    self.input = [AVCaptureDeviceInput deviceInputWithDevice:self.device error:&error];
 
     
 
    if (self.input) {
 
        [self.session addInput:self.input];
 
    }else{
 
        NSLog(@"Input Error:%@",error);
 
    }
 
\
 
 
    //预览层的生成
 
    self.previewLayer = [[AVCaptureVideoPreviewLayer alloc] initWithSession:self.session];
 
     
 
    // 直接取用本组件的bounds来做定位,因为本组件的bounds是uniapp传过来的css宽高设置过的
 
    self.previewLayer.frame = self.view.bounds; //预览层填充视图
 
\
 
 
    // AVLayerVideoGravityResizeAspectFill 等比例填充,直到填充满整个视图区域,其中一个维度的部分区域会被裁剪
 
    // AVLayerVideoGravityResize 非均匀模式。两个维度完全填充至整个视图区域
 
    // AVLayerVideoGravityResizeAspect 等比例填充,直到一个维度到达区域边界
 
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
 
    [self.view.layer addSublayer:self.previewLayer];
 
\
 
 
    [self.session startRunning];
 
}

一些固定的标注写法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/// 前端更新属性回调方法
 
/// @param attributes 更新的属性
 
- (void)updateAttributes:(NSDictionary *)attributes {
 
    // 解析属性
 
    if (attributes[@"showsTraffic"]) {
 
//        _showsTraffic = [DCUniConvert BOOL: attributes[@"showsTraffic"]];
 
    }
 
}
 
\
 
 
/// 前端注册的事件会调用此方法
 
/// @param eventName 事件名称
 
- (void)addEvent:(NSString *)eventName {
 
    if ([eventName isEqualToString:@"mapLoaded"]) {
 
         
 
    }
 
}
 
\
 
 
/// 对应的移除事件回调方法
 
/// @param eventName 事件名称
 
- (void)removeEvent:(NSString *)eventName {
 
    if ([eventName isEqualToString:@"mapLoaded"]) {
 
         
 
    }
 
}

ios端回调原生方法

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 返回给前端的信息回调
 
// 向前端发送事件,params 为传给前端的数据 注:数据最外层为 NSDictionary 格式,需要以 "detail" 作为 key 值
 
- (void) returnFunc:(NSString *) func returnCode:(NSNumber *)code returnMess:(NSString *) message{
 
    NSString *imgUrl = self.imagePath ? self.imagePath : @"";
 
    NSString *vioUrl = self.videoPath ? self.videoPath : @"";
 
\
 
 
    [self fireEvent:func params:@{@"detail":@{@"code":code,@"message":message,@"videoPath":vioUrl,@"imagePath":imgUrl}} domChanges:nil];
 
}

拍照、录像[开始、停止]、闪光灯切换、摄像头镜头切换、设置水印内容等功能接口

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
// 下列为暴露出来的方法列表 START
 
// 通过 WX_EXPORT_METHOD 将方法暴露给前端
 
UNI_EXPORT_METHOD(@selector(openFlash))
 
// 开启闪光灯
 
- (void)openFlash {
 
    [self setFlashMode:AVCaptureFlashModeOn];
 
}
 
\
 
 
UNI_EXPORT_METHOD(@selector(closeFlash))
 
// 关闭闪光灯
 
- (void)closeFlash {
 
    [self setFlashMode:AVCaptureFlashModeOff];
 
}
 
\
 
 
UNI_EXPORT_METHOD(@selector(autoFlash))
 
// 自动闪光灯
 
- (void)autoFlash {
 
    [self setFlashMode:AVCaptureFlashModeAuto];
 
}
 
\
 
 
UNI_EXPORT_METHOD(@selector(openFront))
 
// 切换前置摄像头
 
- (void)openFront {
 
    [self switchCamer:AVCaptureDevicePositionFront];
 
}
 
\
 
 
UNI_EXPORT_METHOD(@selector(openBack))
 
// 切换后置摄像头
 
- (void)openBack {
 
    [self switchCamer:AVCaptureDevicePositionBack];
 
}
 
\
 
 
// 通过 WX_EXPORT_METHOD 将方法暴露给前端
 
UNI_EXPORT_METHOD(@selector(takePhoto:))
 
// 拍照
 
- (void)takePhoto:(NSDictionary *)options {
 
    // options 为前端传递的参数
 
    NSLog(@"IOS收到开始拍照请求");
 
     
 
    //获取连接
 
    AVCaptureConnection *connection = [self.imageOutput connectionWithMediaType:AVMediaTypeVideo];
 
\
 
 
    //程序只支持纵向,但是如果用户横向拍照时,需要调整结果照片的方向
 
    //判断是否支持设置视频方向
 
    if (connection.isVideoOrientationSupported) {
 
         
 
        //获取方向值
 
        connection.videoOrientation = [self currentVideoOrientation];
 
    }
 
\
 
 
    //定义一个handler 块,会返回1个图片的NSData数据
 
    id handler = ^(CMSampleBufferRef sampleBuffer,NSError *error)
 
                {
 
                    if (sampleBuffer != NULL) {
 
                        NSData *imageData = [AVCaptureStillImageOutput jpegStillImageNSDataRepresentation:sampleBuffer];
 
                        UIImage *image = [[UIImage alloc]initWithData:imageData];
 
                         
 
                        [self returnFunc:@"takePhotoSuccess" returnCode:@200 returnMess:@"拍照成功"];
 
                        //重点:捕捉图片成功后,将图片传递出去
 
                        [self saveImage:image];
 
                    }else
 
                    {
 
                        NSLog(@"保存出错NULL sampleBuffer:%@",[error localizedDescription]);
 
                    }
 
                };
 
     
 
    //捕捉静态图片
 
    [self.imageOutput captureStillImageAsynchronouslyFromConnection:connection completionHandler:handler];
 
}
 
 
 
 
UNI_EXPORT_METHOD(@selector(addWaterText:))
 
// 添加水印
 
- (void)addWaterText:(NSDictionary *)options{
 
    NSLog(@"接收到水印内容:%@",options);
 
     
 
    if(options[@"time"]){
 
        self.timeStr = options[@"time"];
 
    }
 
    if(options[@"date"]){
 
        self.dateStr = options[@"date"];
 
    }
 
    if(options[@"week"]){
 
        self.weekStr = options[@"week"];
 
    }
 
    if(options[@"address"]){
 
        self.addressStr = options[@"address"];
 
    }
 
    if(options[@"remark"]){
 
        self.remarkStr = options[@"remark"];
 
    }
 
    if(options[@"logo"]){
 
        self.logoStr = options[@"logo"];
 
    }
 
}
 
\
 
 
// 停止录制
 
UNI_EXPORT_METHOD(@selector(stopRecord))
 
- (void)stopRecord {
 
    NSLog(@"停止录像");
 
    [self.movieOutput stopRecording];
 
}
 
 
 
 
// 开始录制
 
UNI_EXPORT_METHOD(@selector(startRecord))
 
- (void)startRecord {
 
        NSLog(@"开始录像");
 
 
 
 
        // 获取当前视频捕捉连接信息,用于捕捉视频数据配置一些核心属性
 
        AVCaptureConnection * videoConnection = [self.movieOutput connectionWithMediaType:AVMediaTypeVideo];
 
         
 
        //判断是否支持设置videoOrientation 属性。
 
        if([videoConnection isVideoOrientationSupported])
 
        {
 
            //支持则修改当前视频的方向
 
            videoConnection.videoOrientation = [self currentVideoOrientation];
 
             
 
        }
 
         
 
        //判断是否支持视频稳定 可以显著提高视频的质量。只会在录制视频文件涉及
 
        if([videoConnection isVideoStabilizationSupported])
 
        {
 
            videoConnection.enablesVideoStabilizationWhenAvailable = YES;
 
        }
 
         
 
         
 
        AVCaptureDevice *device = self.input.device;
 
         
 
        //摄像头可以进行平滑对焦模式操作。即减慢摄像头镜头对焦速度。当用户移动拍摄时摄像头会尝试快速自动对焦。
 
        if (device.isSmoothAutoFocusEnabled) {
 
            NSError *error;
 
            if ([device lockForConfiguration:&error]) {
 
                 
 
                device.smoothAutoFocusEnabled = YES;
 
                [device unlockForConfiguration];
 
            }else
 
            {
 
//                [self.delegate deviceConfigurationFailedWithError:error];
 
            }
 
        }
 
         
 
        //查找写入捕捉视频的唯一文件系统URL.
 
//        self.outputURL = [self uniqueURL];
 
        NSLog(@"开始录像2");
 
 
 
 
        //在捕捉输出上调用方法 参数1:录制保存路径  参数2:代理
 
        [self.movieOutput startRecordingToOutputFileURL:[self outPutFileURL] recordingDelegate:self];
 
}
 
// 下列为暴露出来的方法列表 END

到此一款包含Android+IOS两端的Uniapp原生插件完成

附上链接: 前往下载插件和demo实例

效果图:

https://juejin.cn/post/7107058762673815566

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 


__EOF__

  • 本文作者: 林恒
  • 本文链接: https://www.cnblogs.com/smileZAZ/p/16955998.html
  • 关于博主: 评论和私信会在第一时间回复。或者直接私信我。
  • 版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
  • 声援博主: 如果您觉得文章对您有帮助,可以点击文章右下角【推荐】一下。