success
This commit is contained in:
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
+421
@@ -0,0 +1,421 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
+164
@@ -0,0 +1,164 @@
|
||||
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);
|
||||
};
|
||||
+153
@@ -0,0 +1,153 @@
|
||||
//
|
||||
//
|
||||
//
|
||||
//
|
||||
import { UIView } from "UIKit";
|
||||
import { CameraView } from "./camera";
|
||||
import { CameraConfig } from '../interface';
|
||||
import { CameraManager } from "./CameraManager";
|
||||
//原生提供以下属性或方法的实现
|
||||
export default {
|
||||
data(): UTSJSONObject {
|
||||
return {};
|
||||
},
|
||||
/**
|
||||
* 组件名称,也就是开发者使用的标签
|
||||
*/
|
||||
name: "skin-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() {
|
||||
}
|
||||
};
|
||||
/*
|
||||
<view>
|
||||
</view>
|
||||
*/
|
||||
+182
@@ -0,0 +1,182 @@
|
||||
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;
|
||||
};
|
||||
/** 接口调用结束的回调函数(调用成功、失败都会执行) */
|
||||
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;
|
||||
Reference in New Issue
Block a user