From 7da266c2ff91c153c2f218a63fedb6c9508c02a5 Mon Sep 17 00:00:00 2001 From: Xiang Silian Date: Thu, 12 Mar 2026 09:54:31 +0800 Subject: [PATCH] init --- .hbuilderx/launch.json | 14 + App.uvue | 48 ++ index.html | 20 + main.uts | 9 + manifest.json | 64 +++ pages.json | 27 + pages/index/index.uvue | 188 +++++++ static/logo.png | Bin 0 -> 4023 bytes uni.scss | 76 +++ uni_modules/XF-cameraUTS/changelog.md | 24 + uni_modules/XF-cameraUTS/package.json | 114 +++++ uni_modules/XF-cameraUTS/readme.md | 183 +++++++ .../utssdk/app-android/AndroidManifest.xml | 14 + .../utssdk/app-android/CameraManager.uts | 26 + .../utssdk/app-android/camera.uts | 474 ++++++++++++++++++ .../utssdk/app-android/config.json | 20 + .../XF-cameraUTS/utssdk/app-android/index.uts | 252 ++++++++++ .../XF-cameraUTS/utssdk/app-android/index.vue | 152 ++++++ .../utssdk/app-ios/CameraManager.uts | 26 + .../XF-cameraUTS/utssdk/app-ios/camera.uts | 469 +++++++++++++++++ .../XF-cameraUTS/utssdk/app-ios/config.json | 3 + .../XF-cameraUTS/utssdk/app-ios/index.uts | 207 ++++++++ .../XF-cameraUTS/utssdk/app-ios/index.vue | 167 ++++++ uni_modules/XF-cameraUTS/utssdk/interface.uts | 214 ++++++++ 24 files changed, 2791 insertions(+) create mode 100644 .hbuilderx/launch.json create mode 100644 App.uvue create mode 100644 index.html create mode 100644 main.uts create mode 100644 manifest.json create mode 100644 pages.json create mode 100644 pages/index/index.uvue create mode 100644 static/logo.png create mode 100644 uni.scss create mode 100644 uni_modules/XF-cameraUTS/changelog.md create mode 100644 uni_modules/XF-cameraUTS/package.json create mode 100644 uni_modules/XF-cameraUTS/readme.md create mode 100644 uni_modules/XF-cameraUTS/utssdk/app-android/AndroidManifest.xml create mode 100644 uni_modules/XF-cameraUTS/utssdk/app-android/CameraManager.uts create mode 100644 uni_modules/XF-cameraUTS/utssdk/app-android/camera.uts create mode 100644 uni_modules/XF-cameraUTS/utssdk/app-android/config.json create mode 100644 uni_modules/XF-cameraUTS/utssdk/app-android/index.uts create mode 100644 uni_modules/XF-cameraUTS/utssdk/app-android/index.vue create mode 100644 uni_modules/XF-cameraUTS/utssdk/app-ios/CameraManager.uts create mode 100644 uni_modules/XF-cameraUTS/utssdk/app-ios/camera.uts create mode 100644 uni_modules/XF-cameraUTS/utssdk/app-ios/config.json create mode 100644 uni_modules/XF-cameraUTS/utssdk/app-ios/index.uts create mode 100644 uni_modules/XF-cameraUTS/utssdk/app-ios/index.vue create mode 100644 uni_modules/XF-cameraUTS/utssdk/interface.uts diff --git a/.hbuilderx/launch.json b/.hbuilderx/launch.json new file mode 100644 index 0000000..5d03eed --- /dev/null +++ b/.hbuilderx/launch.json @@ -0,0 +1,14 @@ +{ + "version" : "1.0", + "configurations" : [ + { + "playground" : "standard", + "type" : "uni-app:app-ios_simulator" + }, + { + "customPlaygroundType" : "device", + "playground" : "standard", + "type" : "uni-app:app-ios" + } + ] +} diff --git a/App.uvue b/App.uvue new file mode 100644 index 0000000..a3c0caf --- /dev/null +++ b/App.uvue @@ -0,0 +1,48 @@ + + + \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..a429bcb --- /dev/null +++ b/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ + + \ No newline at end of file diff --git a/main.uts b/main.uts new file mode 100644 index 0000000..07ed640 --- /dev/null +++ b/main.uts @@ -0,0 +1,9 @@ +import App from './App.uvue' + +import { createSSRApp } from 'vue' +export function createApp() { + const app = createSSRApp(App) + return { + app + } +} \ No newline at end of file diff --git a/manifest.json b/manifest.json new file mode 100644 index 0000000..4f70d2f --- /dev/null +++ b/manifest.json @@ -0,0 +1,64 @@ +{ + "name": "SkinCamera", + "appid": "__UNI__644401D", + "description": "", + "versionName": "1.0.0", + "versionCode": "100", + "uni-app-x": {}, + /* 快应用特有相关 */ + "quickapp": {}, + /* 小程序特有相关 */ + "mp-weixin": { + "appid": "", + "setting": { + "urlCheck": false + }, + "usingComponents": true + }, + "mp-alipay": { + "usingComponents": true + }, + "mp-baidu": { + "usingComponents": true + }, + "mp-toutiao": { + "usingComponents": true + }, + "uniStatistics": { + "enable": false + }, + "vueVersion": "3", + "app": { + "distribute": { + "icons": { + "android": { + "hdpi": "", + "xhdpi": "", + "xxhdpi": "", + "xxxhdpi": "" + } + } + } + }, + "app-android": { + "distribute": { + "modules": {}, + "icons": { + "hdpi": "", + "xhdpi": "", + "xxhdpi": "", + "xxxhdpi": "" + }, + "splashScreens": { + "default": {} + } + } + }, + "app-ios": { + "distribute": { + "modules": {}, + "icons": {}, + "splashScreens": {} + } + } +} \ No newline at end of file diff --git a/pages.json b/pages.json new file mode 100644 index 0000000..10ab281 --- /dev/null +++ b/pages.json @@ -0,0 +1,27 @@ +{ + "pages": [ //pages数组中第一项表示应用启动页,参考:https://doc.dcloud.net.cn/uni-app-x/collocation/pagesjson.html + { + "path": "pages/index/index", + "style": { + "navigationBarTitleText": "uni-app x" + } + } + ], + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "uni-app x", + "navigationBarBackgroundColor": "#F8F8F8", + "backgroundColor": "#F8F8F8" + }, + "uniIdRouter": {}, + "condition" : { //模式配置,仅开发期间生效 + "current": 0, //当前激活的模式(list 的索引项) + "list": [ + { + "name": "", //模式名称 + "path": "", //启动页面,必选 + "query": "" //启动参数,在页面的onLoad函数里面得到 + } + ] + } +} diff --git a/pages/index/index.uvue b/pages/index/index.uvue new file mode 100644 index 0000000..9609ed2 --- /dev/null +++ b/pages/index/index.uvue @@ -0,0 +1,188 @@ + + + + + \ No newline at end of file diff --git a/static/logo.png b/static/logo.png new file mode 100644 index 0000000000000000000000000000000000000000..b5771e209bb677e2ebd5ff766ad5ee11790f305a GIT binary patch literal 4023 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 SYs}WS&%*SCNx89m_ + +``` + +## 参数说明: + +position: + +- 类型:字符串型 +- 描述:(可选项)前置或者后置相机。(front前置 |back 后置) + +flash: + +- 类型:字符串型 +- 描述:(可选项)是否开启闪光灯。( auto, on, off, torch) + + +## **插件接口** + +
+ +# **takePhoto** + +拍照 + +## uni-app项目中(nvue)调用示例: + +```js +import { takePhoto } from "@/uni_modules/XF-cameraUTS" + +takePhoto({ + success: (res) => { + console.log(res) + } +}); +``` + +## uni-app x项目(uvue)中调用示例: + +```js +import { takePhoto } from "@/uni_modules/XF-cameraUTS"; +import { TakePhotoOption, TakePhotoSuccessCallbackResult } from "@/uni_modules/XF-cameraUTS/utssdk/interface.uts"; + +let options = { + success: (res : TakePhotoSuccessCallbackResult) => { + console.log(res) + } +} as TakePhotoOption; +takePhoto(options); +``` + +## 可用性 + +iOS、Android系统 + +可提供的1.0.0及更高版本 + + +
+ +# **startRecord** + +开始录像 + +## uni-app项目中(nvue)调用示例: + +```js +import { startRecord } from "@/uni_modules/XF-cameraUTS" + +startRecord({ + success: (res) => { + console.log(res) + } +}); +``` + +## uni-app x项目(uvue)中调用示例: + +```js +import { startRecord } from "@/uni_modules/XF-cameraUTS"; +import { CameraContextStartRecordOption, GeneralCallbackResult } from "@/uni_modules/XF-cameraUTS/utssdk/interface.uts"; + +let options = { + success: (res : GeneralCallbackResult) => { + console.log(res) + } +} as CameraContextStartRecordOption; +startRecord(options); +``` + +## 可用性 + +iOS、Android系统 + +可提供的1.0.0及更高版本 + + + +
+ +# **stopRecord** + +停止录像 + +## uni-app项目中(nvue)调用示例: + +```js +import { stopRecord } from "@/uni_modules/XF-cameraUTS" + +stopRecord({ + success: (res) => { + console.log(res) + } +}); +``` + +## uni-app x项目(uvue)中调用示例: + +```js +import { stopRecord } from "@/uni_modules/XF-cameraUTS"; +import { CameraContextStopRecordOption, StopRecordSuccessCallbackResult } from "@/uni_modules/XF-cameraUTS/utssdk/interface.uts"; + +let options = { + success: (res : StopRecordSuccessCallbackResult) => { + console.log(res) + } +} as CameraContextStopRecordOption; +stopRecord(options); +``` + +## 可用性 + +iOS、Android系统 + +可提供的1.0.0及更高版本 + + +
+ +# **switchCamera** + +切换相机 + +## uni-app项目中(nvue)调用示例: + +```js +import { switchCamera } from "@/uni_modules/XF-cameraUTS" + +switchCamera({ + position: 'back', + success: (res) => { + console.log(res) + } +}); +``` + +## uni-app x项目(uvue)中调用示例: + +```js +import { switchCamera } from "@/uni_modules/XF-cameraUTS"; +import { SwitchOption, GeneralCallbackResult } from "@/uni_modules/XF-cameraUTS/utssdk/interface.uts"; + +let options = { + position: 'back', + success: (res : GeneralCallbackResult) => { + console.log(res) + } +} as SwitchOption; +switchCamera(options); +``` + +## 可用性 + +iOS、Android系统 + +可提供的1.0.8及更高版本 \ No newline at end of file diff --git a/uni_modules/XF-cameraUTS/utssdk/app-android/AndroidManifest.xml b/uni_modules/XF-cameraUTS/utssdk/app-android/AndroidManifest.xml new file mode 100644 index 0000000..86f5b64 --- /dev/null +++ b/uni_modules/XF-cameraUTS/utssdk/app-android/AndroidManifest.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/uni_modules/XF-cameraUTS/utssdk/app-android/CameraManager.uts b/uni_modules/XF-cameraUTS/utssdk/app-android/CameraManager.uts new file mode 100644 index 0000000..fb0aafc --- /dev/null +++ b/uni_modules/XF-cameraUTS/utssdk/app-android/CameraManager.uts @@ -0,0 +1,26 @@ +import { CameraView } from "./camera"; + +export class CameraManager { + private cameraView : CameraView | null = null; + + private static instance : CameraManager | null = null; + + public static getInstance() : CameraManager { + if (CameraManager.instance == null) { + CameraManager.instance = new CameraManager(); + } + return CameraManager.instance!; + } + + public setCameraView(cameraView ?: CameraView) : void { + this.cameraView = cameraView! + } + + public getCameraView() : CameraView | null { + return this.cameraView; + } + + public dispose() : void { + this.cameraView = null; + } +} \ No newline at end of file diff --git a/uni_modules/XF-cameraUTS/utssdk/app-android/camera.uts b/uni_modules/XF-cameraUTS/utssdk/app-android/camera.uts new file mode 100644 index 0000000..e223355 --- /dev/null +++ b/uni_modules/XF-cameraUTS/utssdk/app-android/camera.uts @@ -0,0 +1,474 @@ +import ProcessCameraProvider from 'androidx.camera.lifecycle.ProcessCameraProvider' +import Preview from 'androidx.camera.core.Preview' +import MirrorMode from 'androidx.camera.core.MirrorMode' +import PreviewView from 'androidx.camera.view.PreviewView' +import Context from 'android.content.Context' +import FrameLayout from 'android.widget.FrameLayout' +import ViewGroup from 'android.view.ViewGroup' +import CameraSelector from 'androidx.camera.core.CameraSelector' +import ImageAnalysis from 'androidx.camera.core.ImageAnalysis' +import UseCaseGroup from 'androidx.camera.core.UseCaseGroup' +import ContextCompat from 'androidx.core.content.ContextCompat' + +import LifecycleOwner from 'androidx.lifecycle.LifecycleOwner' +import ImageCapture from 'androidx.camera.core.ImageCapture' +import Size from 'android.util.Size' +import ImageCaptureException from 'androidx.camera.core.ImageCaptureException' +import File from 'java.io.File' + +import ImageProxy from 'androidx.camera.core.ImageProxy'; +import CameraControl from 'androidx.camera.core.CameraControl'; + +import VideoCapture from 'androidx.camera.video.VideoCapture' +import Recorder from 'androidx.camera.video.Recorder' +import Recording from 'androidx.camera.video.Recording' +import FileOutputOptions from 'androidx.camera.video.FileOutputOptions' +import Quality from 'androidx.camera.video.Quality' +import QualitySelector from 'androidx.camera.video.QualitySelector' +import VideoRecordEvent from 'androidx.camera.video.VideoRecordEvent' + +// import Bitmap from 'android.graphics.Bitmap'; +// import FileOutputStream from 'java.io.FileOutputStream'; +// import MediaMetadataRetriever from 'android.media.MediaMetadataRetriever'; + +import ExecutorService from 'java.util.concurrent.ExecutorService' +import Executors from 'java.util.concurrent.Executors' +import ImageFormat from 'android.graphics.ImageFormat'; + +import { + TakePhotoOption, + GeneralCallbackResult, + TakePhotoSuccessCallbackResult, + CameraContextSetZoomOption, + SetZoomSuccessCallbackResult, + CameraContextStopRecordOption, + CameraContextStartRecordOption, + StartRecordTimeoutCallbackResult, + StopRecordSuccessCallbackResult, + OnCameraFrameCallback, + OnCameraFrameCallbackResult, + CameraFrameListenerStartOption, + StopOption, + + CameraConfig, + FlashMode, + DevicePosition, + Resolution, + //FrameSize, + QualityType, + OnCameraFrameListenerOption +} from '../interface' +import Bitmap from "android.graphics.Bitmap"; +import FileOutputStream from "java.io.FileOutputStream"; + +const FLASH_MODE_MAP = new Map([ + ['auto', ImageCapture.FLASH_MODE_AUTO], + ['on', ImageCapture.FLASH_MODE_ON], + ['torch', ImageCapture.FLASH_MODE_ON], + ['off', ImageCapture.FLASH_MODE_OFF], +]) +const DEVICE_POSITION_MAP = new Map([ + ['back', CameraSelector.LENS_FACING_BACK], + ['front', CameraSelector.LENS_FACING_FRONT], +]) + + +const RESOLUTION_MAP = new Map([ + ['low', Quality.LOWEST], + ['medium', Quality.HD], + ['high', Quality.HIGHEST], +]) + +const QUALITY_MAP = new Map([ + ["high", ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY], + ["low", ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY], +]) + +const MIRROR_MODE = new Map([ + [true, MirrorMode.MIRROR_MODE_ON], + [false, MirrorMode.MIRROR_MODE_OFF], +]) + +export class CameraView extends FrameLayout { + private previewView : PreviewView; + public cameraProvider : ProcessCameraProvider | null = null; + public cameraControl : CameraControl | null = null; + private imageCapture : ImageCapture | null = null + private videoCapture : VideoCapture | null = null + private recording : Recording | null = null + private comp : UTSComponent; + private flashMode : FlashMode = 'auto' + private devicePosition : DevicePosition = 'back' + private resolution : Resolution = 'medium' + // private frameSize : FrameSize = 'medium' + private quality : QualityType = 'normal' + private videoFile : File | null = null + maxZoom : Float = (1).toFloat() + minZoom : Float = (1).toFloat() + private initialized : boolean = false + private isListening : boolean = false + private videoSelfieMirror : boolean | null = null + private cameraContextStopRecordOption : CameraContextStopRecordOption | null = null + private onCameraFrameCallback : OnCameraFrameCallback | null = null + private cameraExecutor : ExecutorService | null = null + private onCameraFrameListenerOption : OnCameraFrameListenerOption | null = null; + + constructor(context : Context, comp : UTSComponent, config : CameraConfig = {} as CameraConfig) { + super(context); + this.comp = comp + this.flashMode = config.flash ?? 'auto' + this.devicePosition = config.devicePosition ?? 'back' + this.resolution = config.resolution ?? 'medium' + // this.frameSize = config.frameSize ?? 'medium' + + this.previewView = new PreviewView(context); + let layoutParam = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT); + this.addView(this.previewView, layoutParam); + this.setupCamera() + } + setupCamera() { + // this.checkSelfPermission().then(() => { + let cameraProviderFuture = ProcessCameraProvider.getInstance(UTSAndroid.getUniActivity()!) + cameraProviderFuture.addListener(() => { + try { + this.cameraProvider = cameraProviderFuture.get() + this.bindCamera(); + } catch (e) { + let ret : Map = new Map(); + ret.set("errMsg", JSON.stringify(e)) + this.comp.$emit('error', ret) + } + }, ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!)) + // }).catch((err) => { + // let ret : Map = new Map(); + // ret.set("errMsg", err ?? '权限不足') + // this.comp.$emit('error', ret) + // }) + } + bindCamera() { + + let targetResolution = RESOLUTION_MAP.get(this.resolution) ?? Quality.HD; + let captureMode = QUALITY_MAP.get(this.quality) ?? ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY; + + // 创建一个Preview对象,用于显示相机预览 + let preview = new Preview.Builder() + .build(); + preview.setSurfaceProvider(this.previewView.getSurfaceProvider()); + + // 创建一个ImageCapture对象,用于拍照 + let imageCapture = new ImageCapture.Builder() + .setFlashMode(FLASH_MODE_MAP.get(this.flashMode)!) + .setCaptureMode(captureMode) + .build(); + // 选择要使用的相机(后置或前置) + let cameraSelector = new CameraSelector.Builder() + .requireLensFacing(DEVICE_POSITION_MAP.get(this.devicePosition)!) + .build(); + + let recorder = Recorder.Builder() + .setQualitySelector(QualitySelector.from(targetResolution)) + let mirrorMode = this.videoSelfieMirror == null ? MirrorMode.MIRROR_MODE_ON_FRONT_ONLY : MIRROR_MODE.get(this.videoSelfieMirror!); + let videoCapture = new VideoCapture.Builder(recorder.build()) + .setMirrorMode(mirrorMode!) + .build(); + + this.videoCapture = videoCapture + + // 将参数应用到 VideoCapture + this.imageCapture = imageCapture; + this.cameraProvider!.unbindAll(); + let imageAnalysis = ImageAnalysis.Builder() + // .setTargetResolution(new Size(1280, 720)) + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + + let viewPort = this.previewView.getViewPort()! + let useCaseGroup = new UseCaseGroup.Builder() + .addUseCase(preview) + .addUseCase(imageAnalysis) + .addUseCase(imageCapture) + .addUseCase(videoCapture) + .setViewPort(viewPort) + .build(); + + this.cameraExecutor = Executors.newSingleThreadExecutor() + let _this = this + class Analyzer implements ImageAnalysis.Analyzer { + constructor() { + + } + override analyze(image : ImageProxy) { + if (_this.isListening) { + let imageFormat = image.getFormat(); + // 获取图像的宽度和高度 + let width = image.width + let height = image.height + + if (imageFormat == ImageFormat.YUV_420_888) { + _this.onCameraFrameCallback?.({ + width, + height, + data: image + } as OnCameraFrameCallbackResult) + + _this.onCameraFrameListenerOption?.success?.({ + width, + height, + data: image + } as OnCameraFrameCallbackResult) + } + } + image.close(); + } + override getDefaultTargetResolution() : Size { + // 返回默认的目标分辨率 + return Size(640, 480) + } + } + imageAnalysis.setAnalyzer(this.cameraExecutor!, new Analyzer() as ImageAnalysis.Analyzer) + + let camera = this.cameraProvider!.bindToLifecycle( + UTSAndroid.getUniActivity()! as LifecycleOwner, + cameraSelector, + useCaseGroup + // preview, + // imageCapture, + // videoCapture + ); + this.cameraControl = camera.getCameraControl(); + if (!this.initialized) { + this.initialized = true + let zoomState = camera.getCameraInfo().getZoomState().getValue(); + this.maxZoom = zoomState?.getMaxZoomRatio() ?? (1).toFloat(); + this.minZoom = zoomState?.getMinZoomRatio() ?? (1).toFloat(); + let maxZoom = this.maxZoom + + let ret : Map = new Map(); + ret.set("maxZoom", maxZoom) + this.comp.$emit('ready', ret) + } + + } + unbindCamera() { + this.cameraProvider?.unbindAll() + this.cameraExecutor?.shutdown() + } + // checkSelfPermission() : Promise { + // let permissionNeed = ["android.permission.CAMERA", "android.permission.RECORD_AUDIO", "android.permission.READ_EXTERNAL_STORAGE"] + // return new Promise((resoleve, _) => { + // resoleve(true); + // UTSAndroid.requestSystemPermission(UTSAndroid.getUniActivity()!, permissionNeed, function (allRight : boolean, list : string[]) { + // if (allRight) { + // resoleve(true); + // } else { + // reject(list); + // } + // }, function (_ : boolean, grantedList : string[]) { + // //grantedList + // reject(grantedList); + // }) + // }) + // } + setConfig(config : CameraConfig = {} as CameraConfig) { + this.flashMode = config.flash ?? 'auto' + this.devicePosition = config.devicePosition ?? 'back' + this.resolution = config.resolution ?? 'medium' + // this.frameSize = config.frameSize ?? 'medium' + } + setFlash(mode : FlashMode) { + this.flashMode = mode + if (FLASH_MODE_MAP.get(mode) == null || this.imageCapture == null) return + this.imageCapture!.setFlashMode(FLASH_MODE_MAP.get(mode)!) + } + switchCamera(position : DevicePosition) { + this.devicePosition = position + if (DEVICE_POSITION_MAP.get(position) == null || this.cameraProvider == null) return + this.unbindCamera() + this.bindCamera() + } + isResolutionSupported(context : Context, cameraSelector : CameraSelector, targetResolution : Size) : boolean { + return true + } + takePhoto(option : TakePhotoOption) { + let quality = option.quality ?? "high"; + this.quality = quality; + if (quality == 'low') { + this.unbindCamera() + this.bindCamera() + } + // 设置输出目录 + let privateDir = UTSAndroid.getAppCachePath()!; + const tempFile = new File(privateDir); + if (!tempFile.exists()) { + tempFile.mkdirs() + } + // 设置文件名 + const photoFile = new File(privateDir, Date.now() + '.png'); + + // 处理镜像 + let metadata = new ImageCapture.Metadata(); + metadata.setReversedHorizontal(option.selfieMirror ?? DEVICE_POSITION_MAP.get(this.devicePosition)! == CameraSelector.LENS_FACING_FRONT); + + const outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).setMetadata(metadata).build() + class OnImageSavedCallback implements ImageCapture.OnImageSavedCallback { + override onImageSaved(outputFileResults : ImageCapture.OutputFileResults) { + option.success?.({ + tempImagePath: photoFile.getAbsolutePath(), + quality: quality, + errMsg: 'ok' + } as TakePhotoSuccessCallbackResult) + option.complete?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + } + override onError(err : ImageCaptureException) { + const result : GeneralCallbackResult = { + errMsg: err.toString() + } + option.fail?.(result) + option.complete?.(result) + } + override onCaptureStarted() { + + } + } + this.imageCapture!.takePicture(outputOptions, ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!), new OnImageSavedCallback() as ImageCapture.OnImageSavedCallback) + + } + setZoom(option : CameraContextSetZoomOption) { + if (this.cameraControl != null) { + try { + this.cameraControl!.setZoomRatio(option.zoom.toFloat()) + option.success?.({ + zoom: option.zoom, + errMsg: 'ok' + } as SetZoomSuccessCallbackResult) + option.complete?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + } catch (e) { + const result : GeneralCallbackResult = { + errMsg: e.toString() + } + option.complete?.(result) + option.fail?.(result) + } + } else { + const result : GeneralCallbackResult = { + errMsg: '相机不存在' + } + option.complete?.(result) + option.fail?.(result) + } + } + startRecord(option : CameraContextStartRecordOption) { + if (option.selfieMirror != null && this.videoSelfieMirror != option.selfieMirror) { + this.videoSelfieMirror = option.selfieMirror + this.unbindCamera() + this.bindCamera() + } + if (this.recording != null || this.videoCapture == null) { + option.fail?.({ + errMsg: '已经在录制或相机不存在' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '已经在录制或相机不存在' + } as GeneralCallbackResult) + return + } + // 设置输出目录 + let privateDir = UTSAndroid.getAppCachePath()!; + // 设置文件名 + this.videoFile = new File(privateDir, Date.now() + 'video.mp4'); + let outputFileOptions = new FileOutputOptions.Builder(this.videoFile!).build(); + + + this.recording = this.videoCapture!.output + .prepareRecording(UTSAndroid.getUniActivity()!, outputFileOptions) + .withAudioEnabled() + // .apply(()=>{ + // }) + .start(ContextCompat.getMainExecutor(UTSAndroid.getUniActivity()!), (recordEvent : VideoRecordEvent) => { + if (recordEvent instanceof VideoRecordEvent.Start) { + option.success?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + + if (option.timeout != null && option.timeout! > 0) { + setTimeout(() => { + this.recording?.stop() + }, option.timeout!) + } + } else if (recordEvent instanceof VideoRecordEvent.Finalize) { + this.recording?.close() + this.recording = null + let tempVideoPath = this.videoFile?.getAbsolutePath()! + //let tempThumbPath = extractThumbnail(tempVideoPath) ?? '' + this.cameraContextStopRecordOption?.success?.({ + tempVideoPath, + errMsg: 'ok' + } as StopRecordSuccessCallbackResult) + + option.timeoutCallback?.({ + tempVideoPath, + } as StartRecordTimeoutCallbackResult) + } + }) + } + stopRecord(option : CameraContextStopRecordOption) { + if (this.recording == null) { + option.fail?.({ + errMsg: '未开始录制' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未开始录制' + } as GeneralCallbackResult) + return + } + this.cameraContextStopRecordOption = option + this.recording?.stop() + } + onCameraFrame(callback : OnCameraFrameCallback | null = null) { + this.onCameraFrameCallback = callback + } + + onCameraFrameListener(option : OnCameraFrameListenerOption) { + this.onCameraFrameListenerOption = option + } + + cameraFrameOnStart(option ?: CameraFrameListenerStartOption) { + this.isListening = true + option?.success?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + } + cameraFrameOnStop(option ?: StopOption) { + this.isListening = false + option?.success?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + } + +} + + +// // 从视频文件中提取封面的函数 +// function extractThumbnail(videoUri : string) : string | null { +// let retriever = new MediaMetadataRetriever() +// retriever.setDataSource(videoUri) +// // 提取视频的某一帧作为封面 +// let thumbnail = retriever.frameAtTime +// // 保存封面到文件 +// if (thumbnail != null) { +// let privateDir = UTSAndroid.getAppCachePath(); +// const thumbnailFile = new File(privateDir, Date.now() + '_thumbnail.jpg'); +// let outputStream = FileOutputStream(thumbnailFile) +// thumbnail.compress(Bitmap.CompressFormat.JPEG, 90, outputStream) +// outputStream.close() +// return thumbnailFile.getAbsolutePath() +// } +// retriever.release() +// return null +// } \ No newline at end of file diff --git a/uni_modules/XF-cameraUTS/utssdk/app-android/config.json b/uni_modules/XF-cameraUTS/utssdk/app-android/config.json new file mode 100644 index 0000000..39e876f --- /dev/null +++ b/uni_modules/XF-cameraUTS/utssdk/app-android/config.json @@ -0,0 +1,20 @@ +{ + "minSdkVersion": "19", + "dependencies": [ + // 使用camera2实现的CameraX核心库 + // 定义camerax_version为"1.4.0-alpha04" + // 下面的行是可选的,因为core库通过camera-camera2间接包含 + "androidx.camera:camera-core:1.4.0-alpha04" + "androidx.camera:camera-camera2:1.4.0-alpha04" + // 如果你想要额外使用CameraX生命周期库 + "androidx.camera:camera-lifecycle:1.4.0-alpha04" + // 如果你想要额外使用CameraX视频捕获库 + "androidx.camera:camera-video:1.4.0-alpha04" + // 如果你想要额外使用CameraX视图类 + "androidx.camera:camera-view:1.4.0-alpha04" + // 如果你想要额外添加CameraX ML Kit Vision集成 + // "androidx.camera:camera-mlkit-vision:1.4.0-alpha04" + // 如果你想要额外使用CameraX扩展库 + // "androidx.camera:camera-extensions:1.4.0-alpha04" + ] +} \ No newline at end of file diff --git a/uni_modules/XF-cameraUTS/utssdk/app-android/index.uts b/uni_modules/XF-cameraUTS/utssdk/app-android/index.uts new file mode 100644 index 0000000..c44f570 --- /dev/null +++ b/uni_modules/XF-cameraUTS/utssdk/app-android/index.uts @@ -0,0 +1,252 @@ +import { CameraView } from './camera' +import { + TakePhotoOption, + GeneralCallbackResult, + CameraContextSetZoomOption, + CameraContextStartRecordOption, + CameraContextStopRecordOption, + // CameraFrameListener, + OnCameraFrameCallback, + // OnCameraFrameCallbackResult, + CameraFrameListenerStartOption, + StopOption, + TakePhoto, + SetZoom, + StartRecord, + StopRecord, + StartCameraFrame, + StopCameraFrame, + OnCameraFrameListenerOption, + SwitchOption, + SwitchCamera +} from '../interface' +import { CameraManager } from "./CameraManager"; + + +class CameraFrameListener { + context : CameraContext + constructor(context : CameraContext) { + this.context = context + } + start() : void + start(option ?: CameraFrameListenerStartOption) { + this.context.getCamera().then((camera : CameraView) => { + camera.cameraFrameOnStart(option) + }) + } + stop() : void + stop(option ?: StopOption) { + this.context.getCamera().then((camera : CameraView) => { + camera.cameraFrameOnStop(option) + }) + } +} + +class CameraContext { + private mCameraFrameListener : CameraFrameListener | null = null + constructor() { + } + getCamera() : Promise { + let cameraPreview = CameraManager.getInstance().getCameraView() + if (cameraPreview != null) { + return Promise.resolve(cameraPreview); + } + let resolveFunc : ((res : CameraView) => void) | null = null + let rejectFunc : ((res : string) => void) | null = null + + let maxAttempts : number = 5 + let currentAttempt : number = 0 + + function attemptGetCamera() { + let camera = CameraManager.getInstance().getCameraView() + if (camera !== null) { + resolveFunc?.(camera) + } else if (currentAttempt < maxAttempts) { + currentAttempt++ + setTimeout(() => { + attemptGetCamera() + }, 100) + } else { + rejectFunc?.('Camera not found after multiple attempts') + } + } + return new Promise((resolve, reject) => { + resolveFunc = resolve + rejectFunc = reject + attemptGetCamera() + }) + } + + + takePhoto(option : TakePhotoOption) { + this.getCamera().then((camera : CameraView) => { + camera.takePhoto(option) + }).catch(() => { + option.fail?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + }) + + } + setZoom(option : CameraContextSetZoomOption) { + this.getCamera().then((camera : CameraView) => { + camera.setZoom(option) + }).catch(() => { + option.fail?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + }) + } + startRecord(option : CameraContextStartRecordOption) { + this.getCamera().then((camera : CameraView) => { + camera.startRecord(option) + }).catch(() => { + option.fail?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + }) + } + stopRecord(option : CameraContextStopRecordOption) { + this.getCamera().then((camera : CameraView) => { + camera.stopRecord(option) + }).catch(() => { + option.fail?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + }) + } + onCameraFrame() : CameraFrameListener + onCameraFrame(callback : OnCameraFrameCallback | null = null) : CameraFrameListener { + if (this.mCameraFrameListener == null) { + this.mCameraFrameListener = new CameraFrameListener(this) + } + this.getCamera().then((camera : CameraView) => { + camera.onCameraFrame(callback) + }) + return this.mCameraFrameListener! + } + ////////////////////////////////////////////////////// + onCameraFrameListener(option : OnCameraFrameListenerOption) { + this.getCamera().then((camera : CameraView) => { + camera.onCameraFrameListener(option) + }).catch(() => { + console.log("未找到相机") + }) + } + + cameraFrameOnStart(option : CameraFrameListenerStartOption) { + this.getCamera().then((camera : CameraView) => { + camera.cameraFrameOnStart(option) + }).catch(() => { + option.fail?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + }) + } + + cameraFrameOnStop(option : StopOption) { + this.getCamera().then((camera : CameraView) => { + camera.cameraFrameOnStop(option) + }).catch(() => { + option.fail?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + }) + } + + switchCamera(option : SwitchOption) { + this.getCamera().then((camera : CameraView) => { + camera.switchCamera(option.position) + + option.success?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + }).catch(() => { + option.fail?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + }) + } +} + +let context : CameraContext = new CameraContext(); + +export function createCameraContext() : CameraContext { + return context; +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +export const takePhoto : TakePhoto = function (options : TakePhotoOption) { + UTSAndroid.getDispatcher("main").async(function (_) { + context.takePhoto(options) + }, null) +} + +export const setZoom : SetZoom = function (options : CameraContextSetZoomOption) { + UTSAndroid.getDispatcher("main").async(function (_) { + context.setZoom(options) + }, null) +} + +export const startRecord : StartRecord = function (options : CameraContextStartRecordOption) { + UTSAndroid.getDispatcher("main").async(function (_) { + context.startRecord(options) + }, null) +} + +export const stopRecord : StopRecord = function (options : CameraContextStopRecordOption) { + UTSAndroid.getDispatcher("main").async(function (_) { + context.stopRecord(options) + }, null) +} + +@UTSJS.keepAlive +export function onCameraFrameListener(options : OnCameraFrameListenerOption) { + UTSAndroid.getDispatcher("main").async(function (_) { + context.onCameraFrameListener(options) + }, null) +} + +export const startCameraFrame : StartCameraFrame = function (options : CameraFrameListenerStartOption) { + UTSAndroid.getDispatcher("main").async(function (_) { + context.cameraFrameOnStart(options) + }, null) +} + +export const stopCameraFrame : StopCameraFrame = function (options : StopOption) { + UTSAndroid.getDispatcher("main").async(function (_) { + context.cameraFrameOnStop(options) + }, null) +} + + +export const switchCamera : SwitchCamera = function (options : SwitchOption) { + UTSAndroid.getDispatcher("main").async(function (_) { + context.switchCamera(options) + }, null) +} \ No newline at end of file diff --git a/uni_modules/XF-cameraUTS/utssdk/app-android/index.vue b/uni_modules/XF-cameraUTS/utssdk/app-android/index.vue new file mode 100644 index 0000000..c37a8c4 --- /dev/null +++ b/uni_modules/XF-cameraUTS/utssdk/app-android/index.vue @@ -0,0 +1,152 @@ + + + + + \ No newline at end of file diff --git a/uni_modules/XF-cameraUTS/utssdk/app-ios/CameraManager.uts b/uni_modules/XF-cameraUTS/utssdk/app-ios/CameraManager.uts new file mode 100644 index 0000000..fb0aafc --- /dev/null +++ b/uni_modules/XF-cameraUTS/utssdk/app-ios/CameraManager.uts @@ -0,0 +1,26 @@ +import { CameraView } from "./camera"; + +export class CameraManager { + private cameraView : CameraView | null = null; + + private static instance : CameraManager | null = null; + + public static getInstance() : CameraManager { + if (CameraManager.instance == null) { + CameraManager.instance = new CameraManager(); + } + return CameraManager.instance!; + } + + public setCameraView(cameraView ?: CameraView) : void { + this.cameraView = cameraView! + } + + public getCameraView() : CameraView | null { + return this.cameraView; + } + + public dispose() : void { + this.cameraView = null; + } +} \ No newline at end of file diff --git a/uni_modules/XF-cameraUTS/utssdk/app-ios/camera.uts b/uni_modules/XF-cameraUTS/utssdk/app-ios/camera.uts new file mode 100644 index 0000000..270f31f --- /dev/null +++ b/uni_modules/XF-cameraUTS/utssdk/app-ios/camera.uts @@ -0,0 +1,469 @@ +import { UIImage, UIView } from 'UIKit'; +import { AVCaptureDevice, AVCaptureDeviceInput, AVCaptureSession, AVCaptureVideoPreviewLayer, AVMediaType, AVLayerVideoGravity, AVCaptureVideoDataOutput, AVCaptureVideoDataOutputSampleBufferDelegate, AVCaptureOutput, AVCaptureConnection, AVCapturePhotoOutput, AVCapturePhotoSettings, AVCapturePhotoCaptureDelegate, AVCaptureMovieFileOutput, AVCaptureFileOutputRecordingDelegate, AVCaptureFileOutput } from "AVFoundation"; +import { CMSampleBuffer, CMSampleBufferGetImageBuffer } from 'CoreMedia'; +import { DispatchQueue } from 'Dispatch'; +import { CVPixelBufferGetWidth, CVPixelBufferGetHeight } from "CoreVideo"; +import { NSError, URL } from "Foundation"; + +import { + TakePhotoOption, + GeneralCallbackResult, + TakePhotoSuccessCallbackResult, + CameraContextSetZoomOption, + SetZoomSuccessCallbackResult, + CameraContextStopRecordOption, + CameraContextStartRecordOption, + StartRecordTimeoutCallbackResult, + StopRecordSuccessCallbackResult, + OnCameraFrameCallback, + OnCameraFrameCallbackResult, + OnCameraFrameListenerOption, + CameraFrameListenerStartOption, + StopOption, + + CameraConfig, + FlashMode, + DevicePosition, + Resolution +} from '../interface' +import { AVCapturePhoto } from 'AVFoundation'; + + + +const RESOLUTION_MAP = new Map([ + ['low', AVCaptureSession.Preset.low], + ['medium', AVCaptureSession.Preset.medium], + ['high', AVCaptureSession.Preset.high], +]) + +const FLASH_MODE_MAP = new Map([ + ['auto', AVCaptureDevice.TorchMode.auto], + ['on', AVCaptureDevice.TorchMode.on], + ['torch', AVCaptureDevice.TorchMode.on], + ['off', AVCaptureDevice.TorchMode.off], +]) + + +let fileOutputRecordingDelegate : MyAVCaptureFileOutputRecordingDelegate | null = null; +let capturePhotoCaptureDelegate : MyAVCapturePhotoCaptureDelegate | null = null; +let onCameraFrameListenerOption : OnCameraFrameListenerOption | null = null; +let cameraContextStopRecordOption : CameraContextStopRecordOption | null = null; + +class MyAVCaptureFileOutputRecordingDelegate implements AVCaptureFileOutputRecordingDelegate { + private option : CameraContextStartRecordOption; + + constructor(option : CameraContextStartRecordOption) { + this.option = option; + } + + fileOutput(output : AVCaptureFileOutput, @argumentLabel("didStartRecordingTo") fileURL : URL, @argumentLabel("from") connections : [AVCaptureConnection]) : void { + this.option.success?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + } + + fileOutput(output : AVCaptureFileOutput, @argumentLabel("didFinishRecordingTo") outputFileURL : URL, @argumentLabel("from") connections : [AVCaptureConnection], @argumentLabel("error") error ?: NSError) : void { + if (error == null) { + cameraContextStopRecordOption?.success?.({ + tempVideoPath: outputFileURL.path + "", + errMsg: 'ok' + } as StopRecordSuccessCallbackResult) + + this.option.timeoutCallback?.({ + tempVideoPath: outputFileURL.path + "" + } as StartRecordTimeoutCallbackResult) + } else { + cameraContextStopRecordOption?.fail?.({ + errMsg: '录制失败' + } as GeneralCallbackResult) + cameraContextStopRecordOption?.complete?.({ + errMsg: '录制失败' + } as GeneralCallbackResult) + } + + } + +} + +class MyAVCapturePhotoCaptureDelegate implements AVCapturePhotoCaptureDelegate { + private option : TakePhotoOption; + + constructor(option : TakePhotoOption) { + this.option = option; + } + + captureOutput(output : AVCapturePhotoOutput, @argumentLabel("didFinishProcessingPhoto") photo : AVCapturePhoto, @argumentLabel("error") error : Error) : void { + let imageData = photo.fileDataRepresentation() + if (imageData == null) { + const result : GeneralCallbackResult = { + errMsg: "拍照失败" + } + this.option.fail?.(result) + this.option.complete?.(result) + return; + } + let path = UTSiOS.getDataPath() + (new Date().getTime()).toString() + ".jpg"; + let url = new URL(fileURLWithPath = path); + + let quality = this.option.quality ?? "original" + if (quality == 'original') { + try { + UTSiOS.try(imageData!.write(to = url, options = NSData.WritingOptions.atomic)) + + this.option.success?.({ + tempImagePath: path, + quality: quality, + errMsg: 'ok' + } as TakePhotoSuccessCallbackResult) + this.option.complete?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + } catch (e) { + const result : GeneralCallbackResult = { + errMsg: JSON.stringify(e) + } + this.option.fail?.(result) + this.option.complete?.(result) + } + } else { + let image = UIImage(data = imageData!) + if (image != null) { + let _quality : number = 0.5; + if (quality == 'high') { + _quality = 1.0; + } else if (quality == 'normal') { + _quality = 0.8; + } else if (quality == 'low') { + _quality = 0.5; + } + let data = image!.jpegData(compressionQuality = _quality.toDouble())! + + try { + UTSiOS.try(data.write(to = url, options = NSData.WritingOptions.atomic)) + + this.option.success?.({ + tempImagePath: path, + quality: quality, + errMsg: 'ok' + } as TakePhotoSuccessCallbackResult) + this.option.complete?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + } catch (e) { + const result : GeneralCallbackResult = { + errMsg: JSON.stringify(e) + } + this.option.fail?.(result) + this.option.complete?.(result) + } + } else { + const result : GeneralCallbackResult = { + errMsg: "拍照失败" + } + this.option.fail?.(result) + this.option.complete?.(result) + } + } + } + +} + +export class CameraView implements AVCaptureVideoDataOutputSampleBufferDelegate { + + private captureSession ?: AVCaptureSession; + private videoDeviceInput ?: AVCaptureDeviceInput; + private photoDeviceOutput ?: AVCapturePhotoOutput; + private movieDeviceOutput ?: AVCaptureMovieFileOutput; + private previewLayer ?: AVCaptureVideoPreviewLayer; + private previewView ?: UIView; + private comp ?: UTSComponent; + private flashMode : FlashMode = 'auto' + private devicePosition : DevicePosition = 'back' + private resolution : Resolution = 'medium' + private isListening : boolean = false + private onCameraFrameCallback : OnCameraFrameCallback | null = null; + + constructor(previewView : UIView, comp : UTSComponent, config : CameraConfig = {} as CameraConfig) { + super.init(); + this.previewView = previewView + this.comp = comp + this.flashMode = config.flash ?? 'auto' + this.devicePosition = config.devicePosition ?? 'back' + this.resolution = config.resolution ?? 'medium' + this.setupCamera(); + } + + setupCamera() { + this.captureSession = new AVCaptureSession(); + let targetResolution = RESOLUTION_MAP.get(this.resolution) ?? AVCaptureSession.Preset.high; + this.captureSession!.sessionPreset = targetResolution; + let device = this.getCameraDevice(); + try { + //相机 + let deviceInput = UTSiOS.try(new AVCaptureDeviceInput(device = device)) + if (this.captureSession!.canAddInput(deviceInput)) { + this.captureSession!.addInput(deviceInput); + } + //实时流输出 + let output = new AVCaptureVideoDataOutput(); + output.setSampleBufferDelegate(this, queue = DispatchQueue.main); + if (this.captureSession!.canAddOutput(output)) { + this.captureSession!.addOutput(output); + } + + let photoOutput = new AVCapturePhotoOutput(); + // let photoSettings = new AVCapturePhotoSettings(format = [AVVideoCodecKey = AVVideoCodecType.jpeg]); + // photoOutput.setPreparedPhotoSettingsArray([photoSettings], completionHandler = null); + if (this.captureSession!.canAddOutput(photoOutput)) { + this.captureSession!.addOutput(photoOutput); + } + + let movieOutput = new AVCaptureMovieFileOutput(); + if (this.captureSession!.canAddOutput(movieOutput)) { + this.captureSession!.addOutput(movieOutput); + } + + this.videoDeviceInput = deviceInput; + this.photoDeviceOutput = photoOutput; + this.movieDeviceOutput = movieOutput; + this.previewLayer = new AVCaptureVideoPreviewLayer(session = this.captureSession!) + this.previewLayer!.videoGravity = AVLayerVideoGravity.resizeAspectFill; + this.previewLayer!.frame = this.previewView!.layer.bounds; + this.previewView!.layer.addSublayer(this.previewLayer!) + this.captureSession!.startRunning(); + + let ret : Map = new Map(); + ret.set("maxZoom", device.maxAvailableVideoZoomFactor) + let detail : Map = new Map(); + detail.set("detail", ret) + this.comp?.$emit("ready", detail); + } catch (e) { + let ret : Map = new Map(); + ret.set("errMsg", JSON.stringify(e)) + let detail : Map = new Map(); + detail.set("detail", ret) + this.comp?.$emit("error", detail); + } + } + + getCameraDevice() : AVCaptureDevice { + let cameras = AVCaptureDevice.devices(for = AVMediaType.video) + let frontCamera : AVCaptureDevice | null = null; + let backCamera : AVCaptureDevice | null = null; + + let index = 0; + while (index < cameras.length) { + if (index == cameras.length) { + break; + } + let camera = cameras[index.toInt()] as AVCaptureDevice; + if (camera.position == AVCaptureDevice.Position.back) { + backCamera = camera; + } else { + frontCamera = camera; + } + index++; + } + + if (this.devicePosition == 'back') { + return backCamera!; + } else { + return frontCamera!; + } + } + + captureOutput(output : AVCaptureOutput, @argumentLabel("didOutput") sampleBuffer : CMSampleBuffer, @argumentLabel("from") connection : AVCaptureConnection) : void { + + if (this.isListening) { + let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) + let width = CVPixelBufferGetWidth(imageBuffer!) + let height = CVPixelBufferGetHeight(imageBuffer!); + + const res : OnCameraFrameCallbackResult = { + width: width as Int, + height: height as Int, + data: imageBuffer! + }; + this.onCameraFrameCallback?.(res) + onCameraFrameListenerOption?.success?.({ + width: width as Int, + height: height as Int, + data: imageBuffer! + } as OnCameraFrameCallbackResult) + + // this.onCameraFrameListenerOption?.success({ + // width: width as Int, + // height: height as Int, + // data: imageBuffer! + // } as OnCameraFrameCallbackResult) + + + // if (imageBuffer != null) { + // let ciImage = new CIImage(cvPixelBuffer = imageBuffer!) + // let temporaryContext = new CIContext(options = null) + // let width = CVPixelBufferGetWidth(imageBuffer!) + // let height = CVPixelBufferGetHeight(imageBuffer!); + // let videoImage = temporaryContext.createCGImage(ciImage, from = new CGRect(x = 0, y = 0, width = width, height = height)) + // let image = new UIImage(cgImage = videoImage!) + + // this.onCameraFrameCallback?.({ + // width: width as Int, + // height: height as Int, + // data: image + // } as OnCameraFrameCallbackResult) + // } + } + } + + bindCamera() { + this.captureSession!.startRunning(); + } + + + unbindCamera() { + this.captureSession!.stopRunning(); + } + + + setConfig(config : CameraConfig = {} as CameraConfig) { + this.flashMode = config.flash ?? 'auto' + this.devicePosition = config.devicePosition ?? 'back' + this.resolution = config.resolution ?? 'medium' + } + + setFlash(mode : FlashMode) { + this.flashMode = mode + let targetFlash = FLASH_MODE_MAP.get(this.flashMode) ?? AVCaptureDevice.TorchMode.auto; + try { + let device = this.getCameraDevice(); + if (device.hasTorch && device.isTorchAvailable) { + UTSiOS.try(device.lockForConfiguration()); + device.torchMode = targetFlash; + device.unlockForConfiguration() + } + } catch (e) { + console.log(e) + } + } + + + switchCamera(position : DevicePosition) { + if (!this.captureSession!.isRunning) { + return; + } + this.devicePosition = position; + try { + this.captureSession!.beginConfiguration(); + this.captureSession!.removeInput(this.videoDeviceInput!); + let device = this.getCameraDevice(); + + let deviceInput = UTSiOS.try(new AVCaptureDeviceInput(device = device)) + this.captureSession!.addInput(deviceInput); + this.videoDeviceInput = deviceInput; + this.captureSession!.commitConfiguration(); + } catch (e) { + console.log(e) + } + } + + takePhoto(option : TakePhotoOption) { + capturePhotoCaptureDelegate = new MyAVCapturePhotoCaptureDelegate(option); + let settings = new AVCapturePhotoSettings() + this.photoDeviceOutput?.capturePhoto(with = settings, delegate = capturePhotoCaptureDelegate!); + } + + + setZoom(option : CameraContextSetZoomOption) { + try { + let device = this.getCameraDevice(); + // if (device.isRampingVideoZoom) { + UTSiOS.try(device.lockForConfiguration()); + device.videoZoomFactor = option.zoom.toDouble(); + device.unlockForConfiguration() + + option.success?.({ + zoom: option.zoom, + errMsg: 'ok' + } as SetZoomSuccessCallbackResult) + option.complete?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + // } else { + // const result : GeneralCallbackResult = { + // errMsg: '相机不支持缩放' + // } + // option.complete?.(result) + // option.fail?.(result) + // } + } catch (e) { + const result : GeneralCallbackResult = { + errMsg: JSON.stringify(e) + } + option.complete?.(result) + option.fail?.(result) + } + } + + startRecord(option : CameraContextStartRecordOption) { + let isRecording = this.movieDeviceOutput?.isRecording ?? false; + if (isRecording) { + option.fail?.({ + errMsg: '已经在录制或相机不存在' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '已经在录制或相机不存在' + } as GeneralCallbackResult) + return + } + + fileOutputRecordingDelegate = new MyAVCaptureFileOutputRecordingDelegate(option); + let path = UTSiOS.getDataPath() + (new Date().getTime()).toString() + ".mp4"; + let url = new URL(fileURLWithPath = path); + this.movieDeviceOutput?.startRecording(to = url, recordingDelegate = fileOutputRecordingDelegate!); + + if (option.timeout != null && option.timeout! > 0) { + setTimeout(() => { + this.movieDeviceOutput?.stopRecording(); + }, (option.timeout!).toInt()) + } + } + + + stopRecord(option : CameraContextStopRecordOption) { + let isRecording = this.movieDeviceOutput?.isRecording ?? false; + if (!isRecording) { + option.fail?.({ + errMsg: '未开始录制' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未开始录制' + } as GeneralCallbackResult) + return + } + cameraContextStopRecordOption = option; + this.movieDeviceOutput?.stopRecording(); + } + + + onCameraFrame(callback : OnCameraFrameCallback | null = null) { + this.onCameraFrameCallback = callback + } + + onCameraFrameListener(option : OnCameraFrameListenerOption) { + onCameraFrameListenerOption = option + } + + cameraFrameOnStart(option ?: CameraFrameListenerStartOption) { + this.isListening = true + option?.success?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + } + + cameraFrameOnStop(option ?: StopOption) { + this.isListening = false + option?.success?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + } +} \ No newline at end of file diff --git a/uni_modules/XF-cameraUTS/utssdk/app-ios/config.json b/uni_modules/XF-cameraUTS/utssdk/app-ios/config.json new file mode 100644 index 0000000..dfb9a96 --- /dev/null +++ b/uni_modules/XF-cameraUTS/utssdk/app-ios/config.json @@ -0,0 +1,3 @@ +{ + "deploymentTarget": "11" +} \ No newline at end of file diff --git a/uni_modules/XF-cameraUTS/utssdk/app-ios/index.uts b/uni_modules/XF-cameraUTS/utssdk/app-ios/index.uts new file mode 100644 index 0000000..16422bc --- /dev/null +++ b/uni_modules/XF-cameraUTS/utssdk/app-ios/index.uts @@ -0,0 +1,207 @@ +import { + TakePhotoOption, + GeneralCallbackResult, + CameraContextSetZoomOption, + CameraContextStartRecordOption, + CameraContextStopRecordOption, + OnCameraFrameCallback, + CameraFrameListenerStartOption, + StopOption, + TakePhoto, + SetZoom, + StartRecord, + StopRecord, + StartCameraFrame, + StopCameraFrame, + OnCameraFrameListenerOption, + SwitchCamera, + SwitchOption +} from '../interface' +import { CameraManager } from './CameraManager'; + +class CameraFrameListener { + context : CameraContext + constructor(context : CameraContext) { + this.context = context + } + start() : void + start(option ?: CameraFrameListenerStartOption) { + CameraManager.getInstance().getCameraView()?.cameraFrameOnStart(option) + } + stop() : void + stop(option ?: StopOption) { + CameraManager.getInstance().getCameraView()?.cameraFrameOnStop(option) + } +} + +class CameraContext { + private mCameraFrameListener : CameraFrameListener | null = null + + constructor() { + + } + + takePhoto(option : TakePhotoOption) { + let camera = CameraManager.getInstance().getCameraView() + if (camera == null) { + option.fail?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + return; + } + camera?.takePhoto(option) + } + + setZoom(option : CameraContextSetZoomOption) { + let camera = CameraManager.getInstance().getCameraView() + if (camera == null) { + option.fail?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + return; + } + + camera?.setZoom(option) + } + + startRecord(option : CameraContextStartRecordOption) { + let camera = CameraManager.getInstance().getCameraView() + if (camera == null) { + option.fail?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + return; + } + camera?.startRecord(option) + } + + stopRecord(option : CameraContextStopRecordOption) { + let camera = CameraManager.getInstance().getCameraView() + if (camera == null) { + option.fail?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + return; + } + camera?.stopRecord(option) + } + + onCameraFrame() : CameraFrameListener + onCameraFrame(callback : OnCameraFrameCallback | null = null) : CameraFrameListener { + if (this.mCameraFrameListener == null) { + this.mCameraFrameListener = new CameraFrameListener(this) + } + CameraManager.getInstance().getCameraView()?.onCameraFrame(callback) + return this.mCameraFrameListener! + } + ////////////////////////////////////////////////////// + onCameraFrameListener(option : OnCameraFrameListenerOption) { + let camera = CameraManager.getInstance().getCameraView() + if (camera == null) { + return; + } + camera?.onCameraFrameListener(option) + } + + cameraFrameOnStart(option : CameraFrameListenerStartOption) { + let camera = CameraManager.getInstance().getCameraView() + if (camera == null) { + option.fail?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + return; + } + camera?.cameraFrameOnStart(option) + } + + cameraFrameOnStop(option : StopOption) { + let camera = CameraManager.getInstance().getCameraView() + if (camera == null) { + option.fail?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + return; + } + camera?.cameraFrameOnStop(option) + } + + switchCamera(option : SwitchOption) { + let camera = CameraManager.getInstance().getCameraView() + if (camera == null) { + option.fail?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: '未找到相机' + } as GeneralCallbackResult) + return; + } + camera?.switchCamera(option.position) + + option.success?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + option.complete?.({ + errMsg: 'ok' + } as GeneralCallbackResult) + } +} + +let context : CameraContext = new CameraContext(); + +export function createCameraContext() : CameraContext { + return context; +} + + +////////////////////////////////////////////////////////////////////////////////////////////////// +export const takePhoto : TakePhoto = function (options : TakePhotoOption) { + context.takePhoto(options) +} + +export const setZoom : SetZoom = function (options : CameraContextSetZoomOption) { + context.setZoom(options) +} + +export const startRecord : StartRecord = function (options : CameraContextStartRecordOption) { + context.startRecord(options) +} + +export const stopRecord : StopRecord = function (options : CameraContextStopRecordOption) { + context.stopRecord(options) +} + +@UTSJS.keepAlive +export function onCameraFrameListener(options : OnCameraFrameListenerOption) { + context.onCameraFrameListener(options) +} + +export const startCameraFrame : StartCameraFrame = function (options : CameraFrameListenerStartOption) { + context.cameraFrameOnStart(options) +} + +export const stopCameraFrame : StopCameraFrame = function (options : StopOption) { + context.cameraFrameOnStop(options) +} + +export const switchCamera : SwitchCamera = function (options : SwitchOption) { + context.switchCamera(options) +} \ No newline at end of file diff --git a/uni_modules/XF-cameraUTS/utssdk/app-ios/index.vue b/uni_modules/XF-cameraUTS/utssdk/app-ios/index.vue new file mode 100644 index 0000000..fed3abc --- /dev/null +++ b/uni_modules/XF-cameraUTS/utssdk/app-ios/index.vue @@ -0,0 +1,167 @@ + + + + \ No newline at end of file diff --git a/uni_modules/XF-cameraUTS/utssdk/interface.uts b/uni_modules/XF-cameraUTS/utssdk/interface.uts new file mode 100644 index 0000000..ae3b66f --- /dev/null +++ b/uni_modules/XF-cameraUTS/utssdk/interface.uts @@ -0,0 +1,214 @@ +export type GeneralCallbackResult = { + /** 错误信息 */ + errMsg : string +} +export type TakePhotoSuccessCallbackResult = { + /** 照片文件的临时路径 (本地路径),安卓是jpg图片格式,ios是png */ + tempImagePath : string + quality : string + errMsg : string +} +/** 接口调用失败的回调函数 */ +export type TakePhotoFailCallback = (res : GeneralCallbackResult) => void +/** 接口调用成功的回调函数 */ +export type TakePhotoSuccessCallback = ( + result : TakePhotoSuccessCallbackResult +) => void + +/** 接口调用结束的回调函数(调用成功、失败都会执行) */ +export type TakePhotoCompleteCallback = (res : GeneralCallbackResult) => void + +export type TakePhotoOption = { + /** 接口调用结束的回调函数(调用成功、失败都会执行) */ + complete ?: TakePhotoCompleteCallback + /** 接口调用失败的回调函数 */ + fail ?: TakePhotoFailCallback + /** 成像质量 + * + * 可选值: + * - 'high': 高质量; + * - 'normal': 普通质量; + * - 'low': 低质量; + * - 'original': 原图; */ + quality ?: 'high' | 'normal' | 'low' | 'original' + /** + * 是否开启镜像 */ + selfieMirror ?: boolean + /** 接口调用成功的回调函数 */ + success ?: TakePhotoSuccessCallback +} + + +export type SetZoomSuccessCallbackResult = { + /** 实际设置的缩放级别。由于系统限制,某些机型可能无法设置成指定值,会改用最接近的可设值。 */ + zoom : number + errMsg : string +} +/** 接口调用结束的回调函数(调用成功、失败都会执行) */ +export type SetZoomCompleteCallback = (res : GeneralCallbackResult) => void +/** 接口调用失败的回调函数 */ +export type SetZoomFailCallback = (res : GeneralCallbackResult) => void +/** 接口调用成功的回调函数 */ +export type CameraContextSetZoomSuccessCallback = ( + result : SetZoomSuccessCallbackResult +) => void + +export type CameraContextSetZoomOption = { + /** 缩放级别,范围[1, maxZoom]。zoom 可取小数,精确到小数后一位。maxZoom 可在 bindinitdone 返回值中获取。 */ + zoom : number + /** 接口调用结束的回调函数(调用成功、失败都会执行) */ + complete ?: SetZoomCompleteCallback + /** 接口调用失败的回调函数 */ + fail ?: SetZoomFailCallback + /** 接口调用成功的回调函数 */ + success ?: CameraContextSetZoomSuccessCallback +} + +export type StartRecordTimeoutCallbackResult = { + /** 视频的文件的临时路径 (本地路径) */ + tempVideoPath : string +} + +/** 接口调用结束的回调函数(调用成功、失败都会执行) */ +export type StartRecordCompleteCallback = (res : GeneralCallbackResult) => void +/** 接口调用失败的回调函数 */ +export type StartRecordFailCallback = (res : GeneralCallbackResult) => void +/** 超过录制时长上限时会结束录像并触发此回调,录像异常退出时也会触发此回调 */ +export type StartRecordTimeoutCallback = ( + result : StartRecordTimeoutCallbackResult +) => void +/** 接口调用成功的回调函数 */ +export type CameraContextStartRecordSuccessCallback = ( + res : GeneralCallbackResult +) => void +export type CameraContextStartRecordOption = { + /** 接口调用结束的回调函数(调用成功、失败都会执行) */ + complete ?: StartRecordCompleteCallback + /** 接口调用失败的回调函数 */ + fail ?: StartRecordFailCallback + /** + * 是否开启镜像 */ + selfieMirror ?: boolean + /** 接口调用成功的回调函数 */ + success ?: CameraContextStartRecordSuccessCallback + /** + * 录制时长上限,单位为秒,最长不能超过 5 分钟 */ + timeout ?: number + /** 超过录制时长上限时会结束录像并触发此回调,录像异常退出时也会触发此回调 */ + timeoutCallback ?: StartRecordTimeoutCallback +} + +export type StopRecordSuccessCallbackResult = { + /** 视频的文件的临时路径 (本地路径) */ + tempVideoPath : string + errMsg : string +} +/** 接口调用结束的回调函数(调用成功、失败都会执行) */ +export type StopRecordCompleteCallback = (res : GeneralCallbackResult) => void +/** 接口调用失败的回调函数 */ +export type StopRecordFailCallback = (res : GeneralCallbackResult) => void +/** 接口调用成功的回调函数 */ +export type CameraContextStopRecordSuccessCallback = ( + result : StopRecordSuccessCallbackResult +) => void +export type CameraContextStopRecordOption = { + /** 接口调用结束的回调函数(调用成功、失败都会执行) */ + complete ?: StopRecordCompleteCallback + /** 启动视频压缩,压缩效果同`chooseVideo` */ + compressed ?: boolean + /** 接口调用失败的回调函数 */ + fail ?: StopRecordFailCallback + /** 接口调用成功的回调函数 */ + success ?: CameraContextStopRecordSuccessCallback +} + +/** 回调函数 */ +export type OnCameraFrameCallback = (result : OnCameraFrameCallbackResult) => void +export type OnCameraFrameCallbackResult = { + /** 图像像素点数据,一维数组,每四项表示一个像素点的 rgba */ + data : any + /** 图像数据矩形的高度 */ + height : number + /** 图像数据矩形的宽度 */ + width : number +} +/** 接口调用成功的回调函数 */ +export type StartSuccessCallback = (res : GeneralCallbackResult) => void +/** 接口调用失败的回调函数 */ +type StartFailCallback = (res : GeneralCallbackResult) => void +/** 接口调用结束的回调函数(调用成功、失败都会执行) */ +type StartCompleteCallback = (res : GeneralCallbackResult) => void +export type CameraFrameListenerStartOption = { + /** 接口调用结束的回调函数(调用成功、失败都会执行) */ + complete ?: StartCompleteCallback + /** 接口调用失败的回调函数 */ + fail ?: StartFailCallback + /** 接口调用成功的回调函数 */ + success ?: StartSuccessCallback + /** [Worker](https://developers.weixin.qq.com/miniprogram/dev/api/worker/Worker.html) + * + * 可选参数。如果需要在 iOS ExperimentalWorker 内监听摄像头帧数据,则需要传入对应 Worker 对象。详情 [Worker.getCameraFrameData](https://developers.weixin.qq.com/miniprogram/dev/api/worker/Worker.getCameraFrameData.html) */ + // worker ?: Worker +} + + +/** 接口调用结束的回调函数(调用成功、失败都会执行) */ +export type StopCompleteCallback = (res : GeneralCallbackResult) => void +/** 接口调用失败的回调函数 */ +export type StopFailCallback = (res : GeneralCallbackResult) => void +/** 接口调用成功的回调函数 */ +export type StopSuccessCallback = (res : GeneralCallbackResult) => void +export type StopOption = { + /** 接口调用结束的回调函数(调用成功、失败都会执行) */ + complete ?: StopCompleteCallback + /** 接口调用失败的回调函数 */ + fail ?: StopFailCallback + /** 接口调用成功的回调函数 */ + success ?: StopSuccessCallback +} + +export type FlashMode = 'auto' | 'on' | 'off' | 'torch' +export type DevicePosition = 'back' | 'front' +export type Resolution = 'low' | 'medium' | 'high' +export type FrameSize = 'medium' | 'small' | 'large' +export type QualityType = 'high' | 'normal' | 'low' | 'original' + + +export type CameraConfig = { + flash ?: FlashMode + devicePosition ?: DevicePosition + resolution ?: Resolution + frameSize ?: FrameSize +} + +////////////////////////////////////////////////////////////////////////////////////////////////// + +/** 接口调用结束的回调函数(调用成功、失败都会执行) */ +export type SwitchCompleteCallback = (res : GeneralCallbackResult) => void +/** 接口调用失败的回调函数 */ +export type SwitchFailCallback = (res : GeneralCallbackResult) => void +/** 接口调用成功的回调函数 */ +export type SwitchSuccessCallback = (res : GeneralCallbackResult) => void +export type SwitchOption = { + position : DevicePosition + /** 接口调用结束的回调函数(调用成功、失败都会执行) */ + complete ?: SwitchCompleteCallback + /** 接口调用失败的回调函数 */ + fail ?: SwitchFailCallback + /** 接口调用成功的回调函数 */ + success ?: SwitchSuccessCallback +} + +////////////////////////////////////////////////////////////////////////////////////////////////// +export type OnCameraFrameListenerOption = { + success ?: OnCameraFrameCallback +} + +export type TakePhoto = (options : TakePhotoOption) => void +export type SetZoom = (options : CameraContextSetZoomOption) => void +export type StartRecord = (options : CameraContextStartRecordOption) => void +export type StopRecord = (options : CameraContextStopRecordOption) => void +export type OnCameraFrameListener = (options : OnCameraFrameListenerOption) => void +export type StartCameraFrame = (options : CameraFrameListenerStartOption) => void +export type StopCameraFrame = (options : StopOption) => void +export type SwitchCamera = (options : SwitchOption) => void \ No newline at end of file