记录--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
如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。
分类:
uni-app学习笔记
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
· 上周热点回顾(2.17-2.23)