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 // }