This commit is contained in:
xsl
2026-03-12 09:54:31 +08:00
commit 7da266c2ff
24 changed files with 2791 additions and 0 deletions
@@ -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;
}
}
@@ -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<string, AVCaptureSession.Preset>([
['low', AVCaptureSession.Preset.low],
['medium', AVCaptureSession.Preset.medium],
['high', AVCaptureSession.Preset.high],
])
const FLASH_MODE_MAP = new Map<string, AVCaptureDevice.TorchMode>([
['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<UIView>;
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<UIView>, 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<string, any> = new Map();
ret.set("maxZoom", device.maxAvailableVideoZoomFactor)
let detail : Map<string, any> = new Map();
detail.set("detail", ret)
this.comp?.$emit("ready", detail);
} catch (e) {
let ret : Map<string, any> = new Map();
ret.set("errMsg", JSON.stringify(e))
let detail : Map<string, any> = 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)
}
}
@@ -0,0 +1,3 @@
{
"deploymentTarget": "11"
}
@@ -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)
}
@@ -0,0 +1,167 @@
<template>
<view>
</view>
</template>
<script lang="uts">
import { UIView } from "UIKit"
import { CameraView } from "./camera";
import { CameraConfig } from '../interface'
import { CameraManager } from "./CameraManager";
//原生提供以下属性或方法的实现
export default {
data() {
return {
};
},
/**
* 组件名称,也就是开发者使用的标签
*/
name: "xf-camera",
/**
* 组件涉及的事件声明,只有声明过的事件,才能被正常发送
*/
emits: ['stop', 'error', 'ready'],
/**
* 属性声明,组件的使用者会传递这些属性值到组件
*/
props: {
"mode": {
type: String,
default: "normal"
},
"resolution": {
type: String,
default: "medium" // low | high
},
"position": {
type: String,
default: "back" // front前置 |back 后置
},
"flash": {
type: String,
default: "auto" // auto, on, off, torch
},
"frameSize": {
type: String,
default: "medium" // small|large
}
},
/**
* 属性变化监听器实现
*/
watch: {
"position": {
/**
* 这里监听属性变化,并进行组件内部更新
*/
handler(newValue : String, _oldValue : String) {
CameraManager.getInstance().getCameraView()?.switchCamera(newValue)
},
/**
* 创建时是否通过此方法更新属性,默认值为false
*/
immediate: false
},
"flash": {
/**
* 这里监听属性变化,并进行组件内部更新
*/
handler(newValue : String, _oldValue : String) {
CameraManager.getInstance().getCameraView()?.setFlash(newValue)
},
/**
* 创建时是否通过此方法更新属性,默认值为false
*/
immediate: false
}
},
/**
* 规则:如果没有配置expose,则methods中的方法均对外暴露,如果配置了expose,则以expose的配置为准向外暴露
* ['publicMethod'] 含义为:只有 `publicMethod` 在实例上可用
*/
expose: [],
methods: {
},
/**
* 组件被创建,组件第一个生命周期,
* 在内存中被占用的时候被调用,开发者可以在这里执行一些需要提前执行的初始化逻辑
* [可选实现]
*/
created() {
},
/**
* 对应平台的view载体即将被创建,对应前端beforeMount
* [可选实现]
*/
NVBeforeLoad() {
},
/**
* 创建原生View,必须定义返回值类型
* 开发者需要重点实现这个函数,声明原生组件被创建出来的过程,以及最终生成的原生组件类型
* [必须实现]
*/
NVLoad() : UIView {
let previewView = new UIView()
previewView.tag = 9527
return previewView
},
/**
* 原生View已创建
* [可选实现]
*/
NVLoaded() {
},
/**
* 原生View布局完成
* [可选实现]
*/
NVLayouted() {
let previewView = this.$el as UIView;
let cameraView = new CameraView(previewView, this, {
resolution: this.resolution,
frameSize: this.frameSize,
devicePosition: this.position,
flash: this.flash
} as CameraConfig)
CameraManager.getInstance().setCameraView(cameraView);
},
/**
* 原生View将释放
* [可选实现]
*/
NVBeforeUnload() {
let ret : Map<string, any> = new Map();
let detail : Map<string, any> = new Map();
detail.set("detail", ret)
this.$emit("stop", detail);
CameraManager.getInstance().getCameraView()?.unbindCamera()
},
/**
* 原生View已释放,这里可以做释放View之后的操作
* [可选实现]
*/
NVUnloaded() {
CameraManager.getInstance().dispose()
},
/**
* 组件销毁
* [可选实现]
*/
unmounted() {
}
}
</script>
<style>
</style>