這里給大家分享我在網上總結出來的一些知識,希望對大家有所幫助

<!-- 相機原生插件 START --> <camera-view ref="cameraObj" :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實作物理功能
// 拍照
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
public class LuanQingCamera extends UniComponent<FrameLayout>
在initComponentHostView這個固定方法回傳一個組件
@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起需一些危險權限要動態申請,因此我們在使用攝像頭前申請
@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 ,"未授予攝像頭|錄制錄音|檔案讀寫權限");
}
});
}
}
}
攝像頭開始預覽,顯示可見的內容
// 開始預覽
@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();
}
}
執行拍照功能
@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
#import <AVFoundation/AVFoundation.h> #import "DCUniComponent.h" NS_ASSUME_NONNULL_BEGIN @interface LQCamera : DCUniComponent @end NS_ASSUME_NONNULL_END
.m檔案實作固定函式,并回傳一個組件
- (UIView *)loadView {
NSLog(@"插件日志:loadView");
return [UIView new];
}
初始化一些攝像頭引數
- (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];
}
一些固定的標注寫法
/// 前端更新屬性回呼方法
/// @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端回呼原生方法
// 回傳給前端的資訊回呼
// 向前端發送事件,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];
}
拍照、錄像[開始、停止]、閃光燈切換、攝像頭鏡頭切換、設定水印內容等功能介面
// 下列為暴露出來的方法串列 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
如果對您有所幫助,歡迎您點個關注,我會定時更新技術檔案,大家一起討論學習,一起進步,

轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/539375.html
標籤:其他
