372 lines
12 KiB
Plaintext
372 lines
12 KiB
Plaintext
import { abilityAccessCtrl, Permissions } from '@kit.AbilityKit';
|
||
import { camera } from '@kit.CameraKit';
|
||
import { BusinessError } from '@kit.BasicServicesKit';
|
||
import { image } from '@kit.ImageKit';
|
||
import fs from '@ohos.file.fs';
|
||
|
||
@Component
|
||
struct CameraViewComponent {
|
||
@Prop mode: string = 'photo'
|
||
@Prop isFront: boolean = false
|
||
@Prop isFlash: boolean = false
|
||
@Prop isTorch: boolean = false
|
||
@Prop correctOrientation: boolean = false
|
||
@Prop @Watch('onCommandChange') command: string = '' // 指令
|
||
|
||
onTake?: (path: string) => void
|
||
|
||
private xComponentCtl: XComponentController = new XComponentController();
|
||
private xComponentSurfaceId: string = '';
|
||
@State imageWidth: number = 1920;
|
||
@State imageHeight: number = 1080;
|
||
private cameraManager: camera.CameraManager | undefined = undefined;
|
||
private cameras: Array<camera.CameraDevice> | Array<camera.CameraDevice> = [];
|
||
private cameraPosition: number = 0
|
||
private isFrontCamera = false
|
||
private cameraInput: camera.CameraInput | undefined = undefined;
|
||
private previewOutput: camera.PreviewOutput | undefined = undefined;
|
||
private photoOutput: camera.PhotoOutput | undefined = undefined;
|
||
private videoSession: camera.VideoSession | undefined = undefined;
|
||
private photoSession: camera.PhotoSession | undefined = undefined;
|
||
private uiContext: UIContext = this.getUIContext();
|
||
private context: Context | undefined = this.uiContext.getHostContext();
|
||
private cameraPermission: Permissions = 'ohos.permission.CAMERA';
|
||
private isTorchOn: boolean = false
|
||
private isFlashOn: boolean = false
|
||
@State isShow: boolean = false;
|
||
|
||
onCommandChange() {
|
||
let command = this.command.split('-')[0]
|
||
if (command == 'open') {
|
||
this.initCamera()
|
||
} else if (command == 'close') {
|
||
this.releaseCamera();
|
||
} else if (command == 'take') {
|
||
this.takePhoto()
|
||
} else if (command == 'switch') {
|
||
this.isFrontCamera = this.isFront
|
||
this.switchCamera()
|
||
} else if (command == 'flash') {
|
||
this.isFlashOn = this.isFlash
|
||
this.onFlash()
|
||
} else if (command == 'torch') {
|
||
this.isTorchOn = this.isTorch
|
||
this.onTorch()
|
||
}
|
||
}
|
||
|
||
async requestPermissionsFn(): Promise<void> {
|
||
let atManager = abilityAccessCtrl.createAtManager();
|
||
if (this.context) {
|
||
let res = await atManager.requestPermissionsFromUser(this.context, [this.cameraPermission]);
|
||
for (let i = 0; i < res.permissions.length; i++) {
|
||
if (this.cameraPermission.toString() === res.permissions[i] && res.authResults[i] === 0) {
|
||
this.isShow = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
async aboutToAppear() {
|
||
await this.requestPermissionsFn();
|
||
}
|
||
|
||
aboutToDisappear(): void {
|
||
this.releaseCamera();
|
||
}
|
||
|
||
// 初始化相机。
|
||
async initCamera(): Promise<void> {
|
||
// console.info(`initCamera previewOutput xComponentSurfaceId:${this.xComponentSurfaceId}`);
|
||
try {
|
||
// 获取相机管理器实例。
|
||
this.cameraManager = camera.getCameraManager(this.context);
|
||
if (!this.cameraManager) {
|
||
console.error('initCamera getCameraManager');
|
||
}
|
||
// 获取当前设备支持的相机device列表。
|
||
this.cameras = this.cameraManager.getSupportedCameras();
|
||
if (!this.cameras) {
|
||
console.error('initCamera getSupportedCameras');
|
||
}
|
||
// 选择一个相机device,创建cameraInput输出对象。
|
||
this.cameraInput = this.cameraManager.createCameraInput(this.cameras[this.cameraPosition]);
|
||
if (!this.cameraInput) {
|
||
console.error('initCamera createCameraInput');
|
||
}
|
||
// 打开相机。
|
||
await this.cameraInput.open().catch((err: BusinessError) => {
|
||
console.error(`initCamera open fail: ${err}`);
|
||
})
|
||
// 获取相机device支持的profile。
|
||
let capability: camera.CameraOutputCapability = this.cameraManager.getSupportedOutputCapability(this.cameras[this.cameraPosition], camera.SceneMode.NORMAL_PHOTO);
|
||
if (!capability) {
|
||
console.error('initCamera getSupportedOutputCapability');
|
||
}
|
||
|
||
let previewProfilesArray: Array<camera.Profile> = capability.previewProfiles;
|
||
if (!previewProfilesArray) {
|
||
console.error("createOutput previewProfilesArray == null || undefined");
|
||
}
|
||
|
||
let photoProfilesArray: Array<camera.Profile> = capability.photoProfiles;
|
||
if (!photoProfilesArray) {
|
||
console.error("createOutput photoProfilesArray == null || undefined");
|
||
}
|
||
|
||
// 预览
|
||
let minRatioDiff: number = 0.1;
|
||
let surfaceRatio: number = this.imageWidth / this.imageHeight; // 最接近16:9宽高比。
|
||
let previewProfile: camera.Profile = previewProfilesArray[0];
|
||
|
||
// // 应用开发者根据实际业务需求选择一个支持的预览流previewProfile。
|
||
// for (let index = 0; index < previewProfilesArray.length; index++) {
|
||
// const tempProfile = previewProfilesArray[index];
|
||
// let tempRatio = tempProfile.size.width >= tempProfile.size.height ?
|
||
// tempProfile.size.width / tempProfile.size.height : tempProfile.size.height / tempProfile.size.width;
|
||
// let currentRatio = Math.abs(tempRatio - surfaceRatio);
|
||
// if (currentRatio <= minRatioDiff && tempProfile.format == camera.CameraFormat.CAMERA_FORMAT_JPEG) {
|
||
// previewProfile = tempProfile;
|
||
// break;
|
||
// }
|
||
// }
|
||
// this.imageWidth = previewProfile.size.width; // 更新xComponent组件的宽。
|
||
// this.imageHeight = previewProfile.size.height; // 更新xComponent组件的高。
|
||
// console.info(`initCamera imageWidth:${this.imageWidth} imageHeight:${this.imageHeight}`);
|
||
|
||
// 使用xComponentSurfaceId创建预览。
|
||
this.previewOutput = this.cameraManager.createPreviewOutput(previewProfile, this.xComponentSurfaceId);
|
||
if (!this.previewOutput) {
|
||
console.error('initCamera createPreviewOutput');
|
||
}
|
||
|
||
// 拍照
|
||
this.photoOutput = this.cameraManager.createPhotoOutput(photoProfilesArray[0]);
|
||
this.setPhotoOutputCb(this.photoOutput);
|
||
|
||
// 创建录像模式相机会话。
|
||
if(this.mode == 'video') {
|
||
this.videoSession = this.cameraManager.createSession(camera.SceneMode.NORMAL_VIDEO) as camera.VideoSession;
|
||
if (!this.videoSession) {
|
||
console.error('initCamera createSession');
|
||
}
|
||
// 开始配置会话。
|
||
this.videoSession.beginConfig();
|
||
// 添加相机设备输入。
|
||
this.videoSession.addInput(this.cameraInput);
|
||
// 添加预览流输出。
|
||
this.videoSession.addOutput(this.previewOutput);
|
||
// 提交会话配置。
|
||
await this.videoSession.commitConfig();
|
||
// 开始启动已配置的输入输出流。
|
||
await this.videoSession.start();
|
||
} else if(this.mode == 'photo') {
|
||
this.photoSession = this.cameraManager.createSession(camera.SceneMode.NORMAL_PHOTO) as camera.PhotoSession;
|
||
if (!this.photoSession) {
|
||
console.error('initCamera createSession');
|
||
}
|
||
// 开始配置会话。
|
||
this.photoSession.beginConfig();
|
||
// 添加相机设备输入。
|
||
this.photoSession.addInput(this.cameraInput);
|
||
// 添加预览流输出。
|
||
this.photoSession.addOutput(this.previewOutput);
|
||
// 添加拍照输出流
|
||
this.photoSession.addOutput(this.photoOutput);
|
||
// 提交会话配置。
|
||
await this.photoSession.commitConfig();
|
||
// 开始启动已配置的输入输出流。
|
||
await this.photoSession.start();
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error(`initCamera fail: ${error}`);
|
||
}
|
||
}
|
||
|
||
// 释放相机。
|
||
async releaseCamera(): Promise<void> {
|
||
try {
|
||
if(this.mode == 'video') {
|
||
// 停止当前会话。
|
||
await this.videoSession?.stop();
|
||
// 释放相机输入流。
|
||
await this.cameraInput?.close();
|
||
// 释放预览输出流。
|
||
await this.previewOutput?.release();
|
||
// 释放会话。
|
||
await this.videoSession?.release();
|
||
} else if(this.mode == 'photo') {
|
||
// 停止当前会话。
|
||
await this.photoSession?.stop();
|
||
// 释放相机输入流。
|
||
await this.cameraInput?.close();
|
||
// 释放预览输出流。
|
||
await this.previewOutput?.release();
|
||
// 释放拍照输出流。
|
||
await this.photoOutput?.release();
|
||
// 释放会话。
|
||
await this.photoSession?.release();
|
||
}
|
||
} catch (error) {
|
||
console.error(`initCamera fail: ${error}`);
|
||
}
|
||
}
|
||
|
||
setPhotoOutputCb(photoOutput: camera.PhotoOutput): void {
|
||
//设置回调之后,调用photoOutput的capture方法,就会将拍照的buffer回传到回调中。
|
||
photoOutput.on('photoAvailable', (errCode: BusinessError, photo: camera.Photo): void => {
|
||
if (errCode || photo === undefined) {
|
||
console.error('getPhoto failed');
|
||
return;
|
||
}
|
||
let imageObj = photo.main;
|
||
imageObj.getComponent(image.ComponentType.JPEG, async (errCode: BusinessError, component: image.Component) => {
|
||
if (errCode || component === undefined) {
|
||
console.error('getComponent failed');
|
||
return;
|
||
}
|
||
let buffer: ArrayBuffer;
|
||
if (component.byteBuffer) {
|
||
buffer = component.byteBuffer;
|
||
} else {
|
||
console.error('byteBuffer is null');
|
||
return;
|
||
}
|
||
|
||
// 生成照片
|
||
try {
|
||
const tempDir = this.context?.cacheDir;
|
||
|
||
const timestamp = new Date().getTime();
|
||
const fileName = `photo_${timestamp}.jpg`;
|
||
const filePath = `${tempDir}/${fileName}`;
|
||
|
||
const file = await fs.open(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
|
||
await fs.write(file.fd, buffer);
|
||
await fs.close(file.fd);
|
||
|
||
this.onTake?.(filePath);
|
||
|
||
} catch (error) {
|
||
console.error('保存临时照片失败:', error);
|
||
} finally {
|
||
imageObj.release();
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
takePhoto() {
|
||
let photoCaptureSetting: camera.PhotoCaptureSetting = {
|
||
quality: camera.QualityLevel.QUALITY_LEVEL_HIGH, // 设置图片质量高。
|
||
rotation: camera.ImageRotation.ROTATION_0 // 设置图片旋转角度0。
|
||
}
|
||
|
||
// 使用当前拍照设置进行拍照。
|
||
this.photoOutput?.capture(photoCaptureSetting, (err: BusinessError) => {
|
||
if (err) {
|
||
console.error(`Failed to capture the photo ${err.message}`);
|
||
return;
|
||
}
|
||
// console.info('Callback invoked to indicate the photo capture request success.');
|
||
});
|
||
}
|
||
|
||
// 切换摄像头
|
||
async switchCamera() {
|
||
if (this.cameras.length < 2) {
|
||
console.error('Only one camera available');
|
||
return;
|
||
}
|
||
|
||
// 切换摄像头位置
|
||
this.cameraPosition = (this.isFrontCamera
|
||
? camera.CameraPosition.CAMERA_POSITION_FRONT
|
||
: camera.CameraPosition.CAMERA_POSITION_BACK) - 1;
|
||
|
||
await this.releaseCamera();
|
||
await this.initCamera();
|
||
}
|
||
|
||
// 手电筒
|
||
onTorch() {
|
||
try {
|
||
let torchSupport = this.cameraManager?.isTorchSupported() ?? false
|
||
if(torchSupport) {
|
||
if(this.cameraPosition == camera.CameraPosition.CAMERA_POSITION_BACK - 1) {
|
||
console.error('back camera not support');
|
||
return
|
||
}
|
||
|
||
this.cameraManager?.setTorchMode(this.isTorchOn ? camera.TorchMode.ON : camera.TorchMode.OFF);
|
||
}
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
// 闪光灯
|
||
onFlash() {
|
||
try {
|
||
let torchSupport = this.cameraManager?.isTorchSupported() ?? false
|
||
if(torchSupport) {
|
||
if(this.cameraPosition == camera.CameraPosition.CAMERA_POSITION_FRONT - 1) {
|
||
console.error('front camera not support');
|
||
return
|
||
}
|
||
|
||
this.cameraManager?.setTorchMode(this.isFlashOn ? camera.TorchMode.AUTO : camera.TorchMode.OFF);
|
||
}
|
||
} catch (error) {
|
||
console.error(error);
|
||
}
|
||
}
|
||
|
||
build() {
|
||
Column() {
|
||
if (this.isShow) {
|
||
XComponent({
|
||
id: 'componentId',
|
||
type: XComponentType.SURFACE,
|
||
controller: this.xComponentCtl
|
||
})
|
||
.onLoad(async () => {
|
||
// 获取组件surfaceId。
|
||
this.xComponentSurfaceId = this.xComponentCtl.getXComponentSurfaceId();
|
||
// 初始化相机,组件实时渲染每帧预览流数据。
|
||
this.initCamera()
|
||
})
|
||
.width(this.uiContext.px2vp(this.imageHeight))
|
||
.height(this.uiContext.px2vp(this.imageWidth))
|
||
}
|
||
}
|
||
.justifyContent(FlexAlign.Center)
|
||
.backgroundColor(Color.Black)
|
||
.height('100%')
|
||
.width('100%')
|
||
}
|
||
}
|
||
|
||
@Builder
|
||
export function CameraView(params: ESObject) {
|
||
Row() {
|
||
CameraViewComponent({
|
||
mode: 'photo',
|
||
isFront: params.isFront,
|
||
isFlash: params.isFlash,
|
||
isTorch: params.isTorch,
|
||
command: params.command,
|
||
correctOrientation: params.correctOrientation,
|
||
onTake: (path: string) => {
|
||
let fun = params.onTake as (path: string) => void
|
||
fun(path)
|
||
}
|
||
})
|
||
.width('100%')
|
||
.height('100%')
|
||
}
|
||
.width('100%')
|
||
.height('100%')
|
||
.attributeModifier(params.attributeUpdater)
|
||
} |