diff --git a/README.en.md b/README.en.md index a5dc45362a8cb0dbdeaf671f783f5863e3d51595..e5bf28909623b8933dc546408b4dc63de03d5224 100644 --- a/README.en.md +++ b/README.en.md @@ -2,7 +2,7 @@ ### Overview -Based on the Camera Kit, this sample implements a range of core camera functionalities such as basic preview, preview image adjustments (switching between the front and rear cameras, flash light, focus, zoom, etc.), advanced preview functionalities (grid line, level, timeout pause, face detection, etc.), dual-channel preview, photographing (such as motion photo and delayed shooting), and video recording. It serves as a comprehensive reference and practice guidance for developing a custom camera service. +Based on the Camera Kit, this sample implements a range of core camera functionalities such as basic preview, preview image adjustments (switching between the front and rear cameras, flash light, focus, zoom, etc.), advanced preview functionalities (grid line, level, timeout pause, face detection, etc.), dual-channel preview, photographing (such as motion photo, delayed shooting and taking photo with the volume key), and video recording. It serves as a comprehensive reference and practice guidance for developing a custom camera service. ### Preview diff --git a/README.md b/README.md index 306b92cc1b7a1fd5a0f1f4f9b12d5f750243642e..27e7bfae3a8a6501f2d0ee434a7c626a42ef0e57 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ ### 介绍 -本示例基于Camera Kit相机服务,使用ArkTS API实现基础预览、预览画面调整(前后置镜头切换、闪光灯、对焦、调焦、设置曝光中心点等)、预览进阶功能(网格线、水平仪、人脸检测、超时暂停等)、双路预览(获取预览帧数据)、拍照(动图拍摄、延迟拍摄等)、录像等核心功能。为开发者提供自定义相机开发的完整参考与实践指导。 +本示例基于Camera Kit相机服务,使用ArkTS API实现基础预览、预览画面调整(前后置镜头切换、闪光灯、对焦、调焦、设置曝光中心点等)、预览进阶功能(网格线、水平仪、人脸检测、超时暂停等)、双路预览(获取预览帧数据)、拍照(动图拍摄、延迟拍摄、音量键拍照等)、录像等核心功能。为开发者提供自定义相机开发的完整参考与实践指导。 ### 效果预览 diff --git a/camera/src/main/ets/cameramanagers/CameraManager.ets b/camera/src/main/ets/cameramanagers/CameraManager.ets index 7f8c1709948bfe864dba08020046ba23177913a2..6625b5f83e0a15bd7fb94f38dc267f53c7450a4a 100644 --- a/camera/src/main/ets/cameramanagers/CameraManager.ets +++ b/camera/src/main/ets/cameramanagers/CameraManager.ets @@ -24,8 +24,8 @@ const TAG = 'CameraManager'; * Camera capability management class, manages various configurations and output streams of the camera. */ export class CameraManager { + public session?: camera.PhotoSession | camera.VideoSession; private cameraManager?: camera.CameraManager; - session?: camera.PhotoSession | camera.VideoSession; private cameraInput?: camera.CameraInput; private outputManagers: OutputManager[] = []; @@ -42,7 +42,7 @@ export class CameraManager { } // Monitor camera status. - addCameraStatusListener() { + addCameraStatusListener(): void { this.cameraManager?.on('cameraStatus', (err: BusinessError, statusInfo: camera.CameraStatusInfo) => { if (err && err.message) { Logger.error(TAG, 'cameraStatus with errorMessage = ' + err.message); @@ -52,7 +52,7 @@ export class CameraManager { }); } - getCameraManager() { + getCameraManager(): camera.CameraManager | undefined { return this.cameraManager; } @@ -61,7 +61,7 @@ export class CameraManager { cameraPosition: camera.CameraPosition, sceneMode: camera.SceneMode, getProfile: (cameraOrientation: number) => camera.Profile - ) { + ): Promise { try { const device = this.getCameraDevice(cameraPosition); if (!device) { @@ -103,7 +103,7 @@ export class CameraManager { } } // Stop and reconfigure the output stream. - async refreshOutput(oldOutput: camera.CameraOutput, newOutput: camera.CameraOutput) { + async refreshOutput(oldOutput: camera.CameraOutput, newOutput: camera.CameraOutput): Promise { try { await this.session?.stop(); this.session?.beginConfig(); @@ -117,7 +117,7 @@ export class CameraManager { } // [Start release] - async release() { + async release(): Promise { try { await this.session?.stop(); for (const outputManager of this.outputManagers) { @@ -135,11 +135,11 @@ export class CameraManager { // [End release] // [Start getCameraDevice] - getCameraDevice(cameraPosition: camera.CameraPosition) { + getCameraDevice(cameraPosition: camera.CameraPosition): camera.CameraDevice | undefined { const cameraDevices = this.cameraManager?.getSupportedCameras(); if (!cameraDevices) { Logger.error(TAG, `Failed to get camera device. cameraPosition: ${cameraPosition}}`); - return; + return undefined; } const device = cameraDevices?.find(device => device.cameraPosition === cameraPosition) || cameraDevices[0]; if (!device) { @@ -151,7 +151,7 @@ export class CameraManager { // [End getCameraDevice] // [Start getZoomRange] - getZoomRange() { + getZoomRange(): number[] { try { return this.session!.getZoomRatioRange(); } catch (exception) { @@ -163,7 +163,7 @@ export class CameraManager { // [End getZoomRange] // [Start setFocusMode] - setFocusMode(focusMode: camera.FocusMode) { + setFocusMode(focusMode: camera.FocusMode): void { try { const isSupported = this.session?.isFocusModeSupported(focusMode); if (!isSupported) { @@ -179,7 +179,7 @@ export class CameraManager { // [End setFocusMode] // [Start setFocusPoint] - setFocusPoint(point: camera.Point) { + setFocusPoint(point: camera.Point): void { try { this.session?.setFocusPoint(point); } catch (e) { @@ -190,7 +190,7 @@ export class CameraManager { // [End setFocusPoint] // [Start setExposureMode] - setExposureMode(exposureMode: camera.ExposureMode) { + setExposureMode(exposureMode: camera.ExposureMode): void { try { const isSupported = this.session?.isExposureModeSupported(exposureMode); if (!isSupported) { @@ -206,7 +206,7 @@ export class CameraManager { // [End setExposureMode] // [Start setMeteringPoint] - setMeteringPoint(point: camera.Point) { + setMeteringPoint(point: camera.Point): void { try { this.session?.setMeteringPoint(point); } catch (e) { @@ -216,7 +216,7 @@ export class CameraManager { // [End setMeteringPoint] - setZoomRatio(zoom: number) { + setZoomRatio(zoom: number): void { try { this.session?.setZoomRatio(zoom); } catch (e) { @@ -225,7 +225,7 @@ export class CameraManager { } // [Start setSmoothZoom] - setSmoothZoom(zoom: number) { + setSmoothZoom(zoom: number): void { try { this.session?.setSmoothZoom(zoom); } catch (e) { @@ -236,7 +236,7 @@ export class CameraManager { // [End setSmoothZoom] // [Start setFlashMode] - setFlashMode(flashMode: camera.FlashMode) { + setFlashMode(flashMode: camera.FlashMode): void { try { const isSupported = this.session?.isFlashModeSupported(flashMode); if (!isSupported) { @@ -251,7 +251,7 @@ export class CameraManager { // [End setFlashMode] - setVideoStabilizationMode(stabilizationMode: camera.VideoStabilizationMode) { + setVideoStabilizationMode(stabilizationMode: camera.VideoStabilizationMode): void { try { const session = this.session as camera.VideoSession; const isSupported: boolean = session.isVideoStabilizationModeSupported(stabilizationMode); diff --git a/camera/src/main/ets/cameramanagers/ImageReceiverManager.ets b/camera/src/main/ets/cameramanagers/ImageReceiverManager.ets index 7328f2f40ba86f41041c9092d89672924c64395e..6cf12993b2e9b58da9e689375f6e46cb5f75dc9f 100644 --- a/camera/src/main/ets/cameramanagers/ImageReceiverManager.ets +++ b/camera/src/main/ets/cameramanagers/ImageReceiverManager.ets @@ -28,16 +28,16 @@ const TAG = 'ImageReceiverManager'; * configuration, and release of this output stream. */ export class ImageReceiverManager implements OutputManager { - output?: camera.PreviewOutput; - isActive: boolean = true; - callback: (px: PixelMap) => void; + public output?: camera.PreviewOutput; + public isActive: boolean = true; + public callback: (px: PixelMap) => void; private position: camera.CameraPosition = camera.CameraPosition.CAMERA_POSITION_BACK; constructor(cb: (px: PixelMap) => void) { this.callback = cb; } - async createOutput(config: CreateOutputConfig) { + async createOutput(config: CreateOutputConfig): Promise { const cameraOutputCap = config.cameraManager?.getSupportedOutputCapability(config.device, config.sceneMode); const displayRatio = config.profile.size.width / config.profile.size.height; const profileWidth = config.profile.size.width; @@ -45,12 +45,12 @@ export class ImageReceiverManager implements OutputManager { .sort((a, b) => Math.abs(a.size.width - profileWidth) - Math.abs(b.size.width - profileWidth)) .find(pf => { const pfDisplayRatio = pf.size.width / pf.size.height; - return pf.format === config.profile.format - && Math.abs(pfDisplayRatio - displayRatio) <= CameraConstant.PROFILE_DIFFERENCE; + return pf.format === config.profile.format && + Math.abs(pfDisplayRatio - displayRatio) <= CameraConstant.PROFILE_DIFFERENCE; }); if (!previewProfile) { Logger.error(TAG, 'Failed to get preview profile'); - return; + return undefined; } const surfaceId = await this.init(config.profile.size); try { @@ -62,7 +62,7 @@ export class ImageReceiverManager implements OutputManager { return this.output; } - async release() { + async release(): Promise { try { await this.output?.release(); } catch (exception) { @@ -72,7 +72,7 @@ export class ImageReceiverManager implements OutputManager { } // [Start init] - async init(size: Size, format = image.ImageFormat.JPEG, capacity = 8) { + async init(size: Size, format = image.ImageFormat.JPEG, capacity = 8): Promise { const receiver = image.createImageReceiver(size, format, capacity); const surfaceId = await receiver.getReceivingSurfaceId(); this.onImageArrival(receiver); @@ -82,7 +82,8 @@ export class ImageReceiverManager implements OutputManager { // [End init] // [Start getPixelMap] - async getPixelMap(imgComponent: image.Component, width: number, height: number, stride: number) { + async getPixelMap(imgComponent: image.Component, width: number, height: number, + stride: number): Promise { if (stride === width) { return await image.createPixelMap(imgComponent.byteBuffer, { size: { height: height, width: width }, @@ -109,6 +110,7 @@ export class ImageReceiverManager implements OutputManager { Logger.info(TAG, 'image arrival'); receiver.readNextImage((err: BusinessError, nextImage: image.Image) => { if (err || nextImage === undefined) { + nextImage?.release(); Logger.error(TAG, 'readNextImage failed'); return; } @@ -129,22 +131,23 @@ export class ImageReceiverManager implements OutputManager { let displayDefault: display.Display | null = null; try { displayDefault = display.getDefaultDisplaySync(); - } catch (exception) { - Logger.error(TAG, `getDefaultDisplaySync failed, code is ${exception.code}, message is ${exception.message}`); - } - const displayRotation = (displayDefault?.rotation ?? 0) * camera.ImageRotation.ROTATION_90; - const rotation = this.output!.getPreviewRotation(displayRotation); - if (this.position === camera.CameraPosition.CAMERA_POSITION_FRONT) { - if (displayRotation === 90 || displayRotation === 270) { - await pixelMap.rotate((rotation + 180) % 360); + const displayRotation = (displayDefault?.rotation ?? 0) * camera.ImageRotation.ROTATION_90; + const rotation = this.output?.getPreviewRotation(displayRotation) || 0; + if (this.position === camera.CameraPosition.CAMERA_POSITION_FRONT) { + if (displayRotation === 90 || displayRotation === 270) { + await pixelMap.rotate((rotation + 180) % 360); + } else { + await pixelMap.rotate(rotation); + } + await pixelMap.flip(true, false); } else { await pixelMap.rotate(rotation); } - await pixelMap.flip(true, false); - } else { - await pixelMap.rotate(rotation); + this.callback(pixelMap); + } catch (exception) { + Logger.error(TAG, + `getDefaultDisplaySync failed, code is ${exception.code}, message is ${exception.message}`); } - this.callback(pixelMap); // [EndExclude onImageArrival] } else { Logger.error(TAG, 'byteBuffer is null'); diff --git a/camera/src/main/ets/cameramanagers/MetadataManager.ets b/camera/src/main/ets/cameramanagers/MetadataManager.ets index c5e9ef1ff93b93ef103646765c4b6c35cf76abc2..071539066e4991458c659141be4375b77bf3317a 100644 --- a/camera/src/main/ets/cameramanagers/MetadataManager.ets +++ b/camera/src/main/ets/cameramanagers/MetadataManager.ets @@ -25,9 +25,9 @@ const TAG_LOG = 'MetadataManager'; * configuration, and release of the output stream. */ export class MetadataManager implements OutputManager { - output?: camera.MetadataOutput; - isActive: boolean = true; - onMetadataObjectsAvailable: (faceBoxArr: camera.Rect[]) => void; + public output?: camera.MetadataOutput; + public isActive: boolean = true; + public onMetadataObjectsAvailable: (faceBoxArr: camera.Rect[]) => void; constructor(onMetadataObjectsAvailable: (faceBoxArr: camera.Rect[]) => void) { this.onMetadataObjectsAvailable = onMetadataObjectsAvailable; @@ -38,9 +38,9 @@ export class MetadataManager implements OutputManager { const cameraOutputCap = config.cameraManager?.getSupportedOutputCapability(config.device, config.sceneMode); if (!cameraOutputCap) { Logger.error(TAG_LOG, 'Failed to get supported output capability.'); - return; + return undefined; } - let metadataObjectTypes: Array = cameraOutputCap!.supportedMetadataObjectTypes; + let metadataObjectTypes: camera.MetadataObjectType[] = cameraOutputCap!.supportedMetadataObjectTypes; try { this.output = config.cameraManager?.createMetadataOutput(metadataObjectTypes); if (this.output) { @@ -81,7 +81,7 @@ export class MetadataManager implements OutputManager { }); } - async release() { + async release(): Promise { try { await this.output?.release(); } catch (exception) { diff --git a/camera/src/main/ets/cameramanagers/PhotoManager.ets b/camera/src/main/ets/cameramanagers/PhotoManager.ets index b0d559d706350a3a46148c3f5c9d086e0778e12f..1235703ff3cb2cc2bf933824da5354fdbb56fab1 100644 --- a/camera/src/main/ets/cameramanagers/PhotoManager.ets +++ b/camera/src/main/ets/cameramanagers/PhotoManager.ets @@ -32,11 +32,11 @@ const TAG_LOG = 'PhotoManager'; * configuration, and release of the output stream */ export class PhotoManager implements OutputManager { - output?: camera.PhotoOutput; - isActive: boolean = true; - context: Context; - isSingle: boolean = false; - location: geoLocationManager.Location | null = null; + public output?: camera.PhotoOutput; + public isActive: boolean = true; + public context: Context; + public isSingle: boolean = false; + public location: geoLocationManager.Location | null = null; private callback: (pixelMap: image.PixelMap, url: string) => void = () => { }; @@ -46,17 +46,16 @@ export class PhotoManager implements OutputManager { this.isSingle = isSingle; } - setIsActive(isActive: boolean) { + setIsActive(isActive: boolean): void { this.isActive = isActive; } - setCallback(callback: (pixelMap: image.PixelMap, url: string) => void) { + setCallback(callback: (pixelMap: image.PixelMap, url: string) => void): void { this.callback = callback; } - async createOutput(config: CreateOutputConfig) { - let cameraPhotoOutput: camera.PhotoOutput | undefined = undefined; - cameraPhotoOutput = this.createPhotoOutput(config.cameraManager, config.device, config.profile); + async createOutput(config: CreateOutputConfig): Promise { + let cameraPhotoOutput = this.createPhotoOutput(config.cameraManager, config.device, config.profile); if (cameraPhotoOutput) { this.output = cameraPhotoOutput; this.setPhotoOutputCallback(this.isSingle); @@ -66,9 +65,9 @@ export class PhotoManager implements OutputManager { // [Start create_photo_output] // Create photo output - public createPhotoOutput(cameraManager: camera.CameraManager|undefined, cameraDevice: camera.CameraDevice, - profile: camera.Profile) { - let cameraPhotoOutput: camera.PhotoOutput | undefined = undefined; + public createPhotoOutput(cameraManager: camera.CameraManager | undefined, cameraDevice: camera.CameraDevice, + profile: camera.Profile): camera.PhotoOutput | undefined { + let cameraPhotoOutput: camera.PhotoOutput | undefined; const cameraOutputCapability = cameraManager?.getSupportedOutputCapability(cameraDevice, camera.SceneMode.NORMAL_PHOTO); let photoProfilesArray: camera.Profile[] | undefined = cameraOutputCapability?.photoProfiles; @@ -85,7 +84,7 @@ export class PhotoManager implements OutputManager { }); if (!photoProfile) { Logger.error(TAG_LOG, 'Failed to get photo profile'); - return; + return undefined; } cameraPhotoOutput = cameraManager?.createPhotoOutput(photoProfile); } catch (error) { @@ -106,17 +105,21 @@ export class PhotoManager implements OutputManager { let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = new photoAccessHelper.MediaAssetChangeRequest(photoAsset); assetChangeRequest.saveCameraPhoto(); - await phAccessHelper.applyChanges(assetChangeRequest); - phAccessHelper.release(); + await phAccessHelper.applyChanges(assetChangeRequest).catch((err: BusinessError) => { + Logger.error(TAG_LOG, `applyChanges failed, code is ${err.code}, message is ${err.message}`); + }); + phAccessHelper.release().catch((err: BusinessError) => { + Logger.error(TAG_LOG, `phAccessHelper.release failed, code is ${err.code}, message is ${err.message}`); + }); } catch (error) { Logger.error(TAG_LOG, `apply saveCameraPhoto failed with error: ${error.code}, ${error.message}`); } } async mediaLibRequestBuffer(photoAsset: photoAccessHelper.PhotoAsset, context: Context, - callback: (pixelMap: image.PixelMap, url: string) => void) { + callback: (pixelMap: image.PixelMap, url: string) => void): Promise { class MediaDataHandler implements photoAccessHelper.MediaAssetDataHandler { - onDataPrepared(data: ArrayBuffer) { + onDataPrepared(data: ArrayBuffer): void { if (data === undefined) { Logger.error(TAG_LOG, 'Error occurred when preparing data'); return; @@ -126,13 +129,13 @@ export class PhotoManager implements OutputManager { callback(pixelMap, photoAsset.uri); }).catch((err: BusinessError) => { Logger.error(TAG_LOG, `createPixelMap err:${err.code}`); - }) + }); } } let requestOptions: photoAccessHelper.RequestOptions = { deliveryMode: photoAccessHelper.DeliveryMode.FAST_MODE, - } + }; const handler = new MediaDataHandler(); try { await photoAccessHelper.MediaAssetManager.requestImageData(context, photoAsset, requestOptions, handler); @@ -141,7 +144,7 @@ export class PhotoManager implements OutputManager { } } - public setPhotoOutputCbDouble(cameraPhotoOutput: camera.PhotoOutput) { + public setPhotoOutputCbDouble(cameraPhotoOutput: camera.PhotoOutput): void { cameraPhotoOutput.on('photoAssetAvailable', async (_err: BusinessError, photoAsset: photoAccessHelper.PhotoAsset): Promise => { let accessHelper: photoAccessHelper.PhotoAccessHelper = @@ -155,13 +158,13 @@ export class PhotoManager implements OutputManager { // [Start set_photo_cb_single] // Set photo callback single - setPhotoOutputCbSingle(photoOutput: camera.PhotoOutput, context: Context) { + setPhotoOutputCbSingle(photoOutput: camera.PhotoOutput, context: Context): void { photoOutput.on('photoAvailable', (errCode: BusinessError, photo: camera.Photo): void => { if (errCode || photo === undefined) { Logger.error(TAG_LOG, 'getPhoto failed'); return; } - this.mediaLibSavePhotoSingle(context, photo.main) + this.mediaLibSavePhotoSingle(context, photo.main); }); } @@ -169,7 +172,7 @@ export class PhotoManager implements OutputManager { // [Start save_photo_single] // Save photo single - mediaLibSavePhotoSingle(context: Context, imageObj: image.Image) { + mediaLibSavePhotoSingle(context: Context, imageObj: image.Image): void { imageObj.getComponent(image.ComponentType.JPEG, async (errCode: BusinessError, component: image.Component) => { if (errCode || component === undefined) { Logger.error(TAG_LOG, 'getComponent failed'); @@ -184,19 +187,23 @@ export class PhotoManager implements OutputManager { let extension: string = 'jpg'; let options: photoAccessHelper.CreateOptions = { title: 'testPhoto' - } + }; try { let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = photoAccessHelper.MediaAssetChangeRequest.createAssetRequest(context, photoType, extension, options); - assetChangeRequest.addResource(photoAccessHelper.ResourceType.IMAGE_RESOURCE, buffer) + assetChangeRequest.addResource(photoAccessHelper.ResourceType.IMAGE_RESOURCE, buffer); assetChangeRequest.saveCameraPhoto(); let accessHelper: photoAccessHelper.PhotoAccessHelper = photoAccessHelper.getPhotoAccessHelper(context); - await accessHelper.applyChanges(assetChangeRequest); + await accessHelper.applyChanges(assetChangeRequest).catch((err: BusinessError) => { + Logger.error(TAG_LOG, `applyChanges failed, code is ${err.code}, message is ${err.message}`); + }); let imageSource = image.createImageSource(buffer); let pixelmap = imageSource.createPixelMapSync(); this.callback(pixelmap, assetChangeRequest.getAsset().uri); - accessHelper.release(); + accessHelper.release().catch((err: BusinessError) => { + Logger.error(TAG_LOG, `accessHelper.release failed, code is ${err.code}, message is ${err.message}`); + }); imageObj.release(); } catch (exception) { Logger.error(TAG_LOG, @@ -207,7 +214,7 @@ export class PhotoManager implements OutputManager { // [End save_photo_single] - setPhotoOutputCallback(isSingle: boolean) { + setPhotoOutputCallback(isSingle: boolean): void { if (!this.output) { return; } @@ -221,7 +228,7 @@ export class PhotoManager implements OutputManager { } preparePhoto(session: camera.Session, zoomRatio?: number, flashMode?: camera.FlashMode, - focusMode?: camera.FocusMode) { + focusMode?: camera.FocusMode): void { const photoSession = session as camera.PhotoSession; this.setPhotoFlash(photoSession, flashMode); this.setPhotoFocus(photoSession, focusMode); @@ -231,10 +238,10 @@ export class PhotoManager implements OutputManager { // [Start set_color_space] // Set color space setColorSpaceBeforeCommitConfig(session: camera.PhotoSession, isHdr: boolean): void { - // The isHdr flag indicates whether HDR mode is enabled, with true representing the use of the DISPLAY_P3 color space. + // The isHdr flag indicates whether HDR mode is enabled, with true representing using the DISPLAY_P3 color space. let colorSpace: colorSpaceManager.ColorSpace = isHdr ? colorSpaceManager.ColorSpace.DISPLAY_P3 : colorSpaceManager.ColorSpace.SRGB; - let colorSpaces: Array = []; + let colorSpaces: colorSpaceManager.ColorSpace[] = []; try { colorSpaces = session.getSupportedColorSpaces(); } catch (error) { @@ -260,11 +267,11 @@ export class PhotoManager implements OutputManager { // [End set_color_space] - public checkFlash(photoSession: camera.PhotoSession) { + public checkFlash(photoSession: camera.PhotoSession, flashMode?: camera.FlashMode): boolean { let flashModeStatus: boolean = false; try { if (photoSession.hasFlash()) { - flashModeStatus = photoSession.isFlashModeSupported(camera.FlashMode.FLASH_MODE_AUTO); + flashModeStatus = photoSession.isFlashModeSupported(flashMode || camera.FlashMode.FLASH_MODE_CLOSE); } } catch (exception) { Logger.error(TAG_LOG, `checkFlash failed, code is ${exception.code}, message is ${exception.message}`); @@ -272,9 +279,9 @@ export class PhotoManager implements OutputManager { return flashModeStatus; } - public setPhotoFlash(photoSession: camera.PhotoSession, flashMode?: camera.FlashMode) { + public setPhotoFlash(photoSession: camera.PhotoSession, flashMode?: camera.FlashMode): void { try { - if (this.checkFlash(photoSession)) { + if (this.checkFlash(photoSession, flashMode)) { photoSession.setFlashMode(flashMode || camera.FlashMode.FLASH_MODE_CLOSE); } } catch (error) { @@ -282,7 +289,7 @@ export class PhotoManager implements OutputManager { } } - public setPhotoFocus(photoSession: camera.PhotoSession, focusMode?: camera.FocusMode) { + public setPhotoFocus(photoSession: camera.PhotoSession, focusMode?: camera.FocusMode): void { const defaultMode = camera.FocusMode.FOCUS_MODE_CONTINUOUS_AUTO; try { let focusModeStatus: boolean = photoSession.isFocusModeSupported(focusMode || defaultMode); @@ -303,7 +310,7 @@ export class PhotoManager implements OutputManager { } } - public setPhotoZoomRatio(photoSession: camera.PhotoSession, zoomRatio?: number) { + public setPhotoZoomRatio(photoSession: camera.PhotoSession, zoomRatio?: number): void { let photoZoomRatio = 0; if (!zoomRatio) { try { @@ -322,8 +329,8 @@ export class PhotoManager implements OutputManager { } } - getSupportedColorSpaces(session: camera.PhotoSession): Array { - let colorSpaces: Array = []; + getSupportedColorSpaces(session: camera.PhotoSession): colorSpaceManager.ColorSpace[] { + let colorSpaces: colorSpaceManager.ColorSpace[] = []; try { colorSpaces = session.getSupportedColorSpaces(); } catch (error) { @@ -348,15 +355,19 @@ export class PhotoManager implements OutputManager { // [Start capture_photo] // Capture photo - public async capture(isFront: boolean) { + public async capture(isFront: boolean): Promise { + if (!this.output) { + Logger.error(TAG_LOG, 'Failed to capture the photo due to photo output undefined'); + return; + } const degree = await this.getPhotoDegree(); - const rotation = this.getPhotoRotation(this.output!, degree); + const rotation = this.getPhotoRotation(this.output, degree); let settings: camera.PhotoCaptureSetting = { quality: camera.QualityLevel.QUALITY_LEVEL_HIGH, rotation, mirror: isFront }; - this.output?.capture(settings, (err: BusinessError) => { + this.output.capture(settings, (err: BusinessError) => { if (err) { Logger.error(TAG_LOG, `Failed to capture the photo. error: ${JSON.stringify(err)}`); return; @@ -375,7 +386,7 @@ export class PhotoManager implements OutputManager { } else { try { // Calculate the inverse tangent value - let sd: Decimal = Decimal.atan2(y, -x) + let sd: Decimal = Decimal.atan2(y, -x); // Convert radian values to angle values; let sc: Decimal = Decimal.round(Number(sd) / Math.PI * 180); // Adjust angle to be relative to vertical orientation @@ -390,7 +401,7 @@ export class PhotoManager implements OutputManager { return deviceDegree; } - private getPhotoDegree() { + private getPhotoDegree(): Promise { const promise: Promise = new Promise(resolve => { try { sensor.once(sensor.SensorId.ACCELEROMETER, (data: sensor.AccelerometerResponse) => { @@ -398,9 +409,10 @@ export class PhotoManager implements OutputManager { resolve(degree); }); } catch (exception) { + Promise.reject(exception); Logger.error(TAG_LOG, `getPhotoDegree failed, code is ${exception.code}, message is ${exception.message}`); } - }) + }); return promise; } @@ -432,17 +444,17 @@ export class PhotoManager implements OutputManager { // [Start photo_release] // Release photo - async release() { - try { - await this.output?.release(); - } catch (exception) { - Logger.error(TAG_LOG, `release failed, code is ${exception.code}, message is ${exception.message}`); - } + async release(): Promise { if (this.isSingle) { this.output?.off('photoAvailable'); } else { this.output?.off('photoAssetAvailable'); } + try { + await this.output?.release(); + } catch (exception) { + Logger.error(TAG_LOG, `release failed, code is ${exception.code}, message is ${exception.message}`); + } this.output = undefined; } diff --git a/camera/src/main/ets/cameramanagers/PreviewManager.ets b/camera/src/main/ets/cameramanagers/PreviewManager.ets index 9c9d5f4a00c96e50fcc3b8a500eb30b6f259e9ee..e3170cf2b19743e6a9397f4e5d3e31faae21079f 100644 --- a/camera/src/main/ets/cameramanagers/PreviewManager.ets +++ b/camera/src/main/ets/cameramanagers/PreviewManager.ets @@ -19,16 +19,16 @@ import { Logger } from 'commons'; import { OutputManager, CreateOutputConfig } from './OutputManager'; import CameraConstant from '../constants/CameraConstants'; -const TAG_LOG = 'PreviewManager' +const TAG_LOG = 'PreviewManager'; /** * Preview output stream management class, responsible for managing the creation, * configuration, and release of the output stream. */ export class PreviewManager implements OutputManager { - output?: camera.PreviewOutput; - isActive: boolean = true; - onPreviewStart: () => void = () => { + public output?: camera.PreviewOutput; + public isActive: boolean = true; + public onPreviewStart: () => void = () => { }; constructor(onPreviewStart: () => void) { @@ -36,7 +36,7 @@ export class PreviewManager implements OutputManager { } // [Start createOutput] - async createOutput(config: CreateOutputConfig) { + async createOutput(config: CreateOutputConfig): Promise { const cameraOutputCap = config.cameraManager?.getSupportedOutputCapability(config.device, config.sceneMode); const displayRatio = config.profile.size.width / config.profile.size.height; const profileWidth = config.profile.size.width; @@ -44,12 +44,12 @@ export class PreviewManager implements OutputManager { .sort((a, b) => Math.abs(a.size.width - profileWidth) - Math.abs(b.size.width - profileWidth)) .find(pf => { const pfDisplayRatio = pf.size.width / pf.size.height; - return pf.format === config.profile.format - && Math.abs(pfDisplayRatio - displayRatio) <= CameraConstant.PROFILE_DIFFERENCE; + return pf.format === config.profile.format && + Math.abs(pfDisplayRatio - displayRatio) <= CameraConstant.PROFILE_DIFFERENCE; }); if (!previewProfile) { Logger.error(TAG_LOG, 'Failed to get preview profile'); - return; + return undefined; } try { this.output = config.cameraManager?.createPreviewOutput(previewProfile, config.surfaceId); @@ -64,13 +64,13 @@ export class PreviewManager implements OutputManager { // [End createOutput] - addOutputListener(output: camera.PreviewOutput) { + addOutputListener(output: camera.PreviewOutput): void { this.addFrameStartEventListener(output); this.addFrameEndEventListener(output); } // [Start onFrame] - addFrameStartEventListener(output: camera.PreviewOutput) { + addFrameStartEventListener(output: camera.PreviewOutput): void { output.on('frameStart', (err: BusinessError) => { if (err !== undefined && err.code !== 0) { Logger.error(TAG_LOG, `FrameStart callback Error, errorMessage: ${err.message}`); @@ -81,10 +81,10 @@ export class PreviewManager implements OutputManager { }); } - addFrameEndEventListener(output: camera.PreviewOutput) { + addFrameEndEventListener(output: camera.PreviewOutput): void { output.on('frameEnd', (err: BusinessError) => { if (err !== undefined && err.code !== 0) { - Logger.error(TAG_LOG, `FrameStart callback Error, errorMessage: ${err.message}`); + Logger.error(TAG_LOG, `frameEnd callback Error, errorMessage: ${err.message}`); return; } Logger.info(TAG_LOG, 'Preview frame end'); @@ -94,7 +94,7 @@ export class PreviewManager implements OutputManager { // [End onFrame] // [Start release] - async release() { + async release(): Promise { try { await this.output?.release(); } catch (exception) { @@ -106,14 +106,14 @@ export class PreviewManager implements OutputManager { // [End release] // [Start getSupportedFrameRates] - getSupportedFrameRates() { + getSupportedFrameRates(): camera.FrameRateRange[] | undefined { return this.output?.getSupportedFrameRates(); } // [End getSupportedFrameRates] // [Start setFrameRate] - setFrameRate(minFps: number, maxFps: number) { + setFrameRate(minFps: number, maxFps: number): void { try { this.output?.setFrameRate(minFps, maxFps); } catch (e) { diff --git a/camera/src/main/ets/cameramanagers/VideoManager.ets b/camera/src/main/ets/cameramanagers/VideoManager.ets index 5615a79f576e3473b5d9baeaf21b650e3babcb1e..18680c7c8235ac8e7d2e7d157d18dbb7dbd7a012 100644 --- a/camera/src/main/ets/cameramanagers/VideoManager.ets +++ b/camera/src/main/ets/cameramanagers/VideoManager.ets @@ -21,6 +21,7 @@ import { sensor } from '@kit.SensorServiceKit'; import { Decimal } from '@kit.ArkTS'; import { image } from '@kit.ImageKit'; import { colorSpaceManager } from '@kit.ArkGraphics2D'; +import { BusinessError } from '@kit.BasicServicesKit'; import { Logger } from 'commons'; import { OutputManager, CreateOutputConfig } from './OutputManager'; import CameraConstant from '../constants/CameraConstants'; @@ -47,18 +48,18 @@ export enum AVRecorderState { * configuration, and release of the output stream */ export class VideoManager implements OutputManager { + public output: camera.VideoOutput | undefined = undefined; + public state: media.AVRecorderState = AVRecorderState.IDLE; + public isActive: boolean = false; private avRecorder: media.AVRecorder | undefined = undefined; private avConfig: media.AVRecorderConfig | undefined = undefined; private avProfile: media.AVRecorderProfile | undefined = undefined; private videoProfile: camera.VideoProfile | undefined = undefined; private context: Context | undefined = undefined; - private cameraPosition: number = 0; + private cameraPosition: number = camera.CameraPosition.CAMERA_POSITION_BACK; private qualityLevel: QualityLevel = QualityLevel.NORMAL; - output: camera.VideoOutput | undefined = undefined; private videoUri: string = ''; private file: fileIo.File | undefined = undefined; - state: media.AVRecorderState = AVRecorderState.IDLE; - isActive: boolean = false; private callback: (pixelMap: image.PixelMap, url: string) => void = () => { }; @@ -66,22 +67,22 @@ export class VideoManager implements OutputManager { this.context = context; } - setIsActive(isActive: boolean) { + setIsActive(isActive: boolean): void { this.isActive = isActive; } - async createOutput(config: CreateOutputConfig) { + async createOutput(config: CreateOutputConfig): Promise { try { this.avRecorder = await media.createAVRecorder(); this.avRecorder.on('stateChange', state => { this.state = state; - Logger.info(TAG_LOG, 'on avRecorder state change: ', state) + Logger.info(TAG_LOG, 'on avRecorder state change: ', state); }); } catch (error) { - Logger.info(TAG_LOG, 'createAVRecorder call failed. error code: %{public}s', error.code); + Logger.error(TAG_LOG, 'createAVRecorder call failed. error code: %{public}s', error.code); } if (this.avRecorder === undefined || this.avRecorder === null) { - return; + return undefined; } this.setVideoProfile(config.cameraManager, config.profile, config.device); @@ -91,28 +92,28 @@ export class VideoManager implements OutputManager { return this.output; } - async prepare() { + async prepare(): Promise { try { if (this.avRecorder?.state === AVRecorderState.IDLE && this.avConfig) { await this.avRecorder.prepare(this.avConfig); Logger.info(TAG_LOG, 'Succeeded in preparing'); } } catch (error) { - Logger.info(TAG_LOG, `Failed to prepare and catch error is ${error.message}`); + Logger.error(TAG_LOG, `Failed to prepare and catch error is ${error.message}`); } } - isSupportMirror() { + isSupportMirror(): boolean | undefined { let isSupported: boolean | undefined = this.output?.isMirrorSupported(); return isSupported; } // [Start start_video] - async start(isFront: boolean) { + async start(isFront: boolean): Promise { try { if (this.avRecorder?.state === AVRecorderState.PREPARED) { if (this.isSupportMirror() && isFront) { - this.output?.enableMirror(true) + this.output?.enableMirror(true); } // [StartExclude start_video] await this.avRecorder.updateRotation(this.getVideoRotation(await this.getGravity())); @@ -128,10 +129,10 @@ export class VideoManager implements OutputManager { // [End start_video] // [Start stop_video] - async stop() { + async stop(): Promise { try { - if (this.avRecorder?.state === AVRecorderState.STARTED - || this.avRecorder?.state === AVRecorderState.PAUSED) { + if (this.avRecorder?.state === AVRecorderState.STARTED || + this.avRecorder?.state === AVRecorderState.PAUSED) { await this.avRecorder.stop(); await this.output?.stop(); const thumbnail = await this.getVideoThumbnail(); @@ -140,42 +141,42 @@ export class VideoManager implements OutputManager { } } } catch (error) { - Logger.info(TAG_LOG, `Failed to stop and catch error is ${error.message}`); + Logger.error(TAG_LOG, `Failed to stop and catch error is ${error.message}`); } } // [End stop_video] // [Start pause_video] - async pause() { + async pause(): Promise { try { if (this.avRecorder?.state === AVRecorderState.STARTED) { await this.avRecorder.pause(); await this.output?.stop(); } } catch (error) { - Logger.info(TAG_LOG, `Failed to pause and catch error is ${error.message}`); + Logger.error(TAG_LOG, `Failed to pause and catch error is ${error.message}`); } } // [End pause_video] // [Start resume_video] - async resume() { + async resume(): Promise { try { if (this.avRecorder?.state === AVRecorderState.PAUSED) { await this.output?.start(); await this.avRecorder.resume(); } } catch (error) { - Logger.info(TAG_LOG, `Failed to resume and catch error is ${error.message}`); + Logger.error(TAG_LOG, `Failed to resume and catch error is ${error.message}`); } } // [End resume_video] // [Start release_video] - async release() { + async release(): Promise { try { await this.avRecorder?.release(); await this.output?.release(); @@ -191,16 +192,16 @@ export class VideoManager implements OutputManager { // [End release_video] - getCurrentOutput() { + getCurrentOutput(): camera.VideoOutput | undefined { return this.output; } - setVideoCallback(callback: (pixelMap: image.PixelMap, url: string) => void) { + setVideoCallback(callback: (pixelMap: image.PixelMap, url: string) => void): void { this.callback = callback; } // [Start create_video_output] - async createVideoOutput(cameraManager: camera.CameraManager|undefined) { + async createVideoOutput(cameraManager: camera.CameraManager | undefined): Promise { if (!this.avRecorder || this.avRecorder.state !== AVRecorderState.PREPARED) { return; } @@ -215,8 +216,8 @@ export class VideoManager implements OutputManager { } } - setVideoProfile(cameraManager: camera.CameraManager|undefined, targetProfile: camera.Profile, - device: camera.CameraDevice) { + setVideoProfile(cameraManager: camera.CameraManager | undefined, targetProfile: camera.Profile, + device: camera.CameraDevice): void { this.cameraPosition = device.cameraPosition; let cameraOutputCap: camera.CameraOutputCapability | undefined = cameraManager?.getSupportedOutputCapability(device, @@ -249,10 +250,10 @@ export class VideoManager implements OutputManager { getCameraImageRotation(): camera.ImageRotation { return this.cameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT ? camera.ImageRotation.ROTATION_270 - : camera.ImageRotation.ROTATION_90 + : camera.ImageRotation.ROTATION_90; } - async setAVConfig() { + async setAVConfig(): Promise { // [Start create_file] let options: photoAccessHelper.CreateOptions = { title: Date.now().toString() @@ -266,6 +267,11 @@ export class VideoManager implements OutputManager { } // [End create_file] + if (!this.file) { + Logger.error(TAG_LOG, 'createAsset failed, file undefined'); + return; + } + // [Start av_profile] this.avProfile = { audioBitrate: 48000, // Audio bitrate (unit: bps), which affects audio quality @@ -275,11 +281,13 @@ export class VideoManager implements OutputManager { fileFormat: media.ContainerFormatType.CFT_MPEG_4, // Container Format Configuration videoBitrate: 32000000, // Video bitrate (unit: bps) determines video clarity // Dynamic Selection of Video Encoding Format - videoCodec: (this.qualityLevel === QualityLevel.HIGHER && this.cameraPosition === 0) ? + videoCodec: (this.qualityLevel === QualityLevel.HIGHER && + this.cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK) ? media.CodecMimeType.VIDEO_HEVC : media.CodecMimeType.VIDEO_AVC, videoFrameWidth: this.videoProfile?.size.width, // Obtain width from video configuration videoFrameHeight: this.videoProfile?.size.height, // Obtain height from video configuration - videoFrameRate: this.cameraPosition === 0 ? 60 : 30, // Obtain rate from video configuration + videoFrameRate: this.cameraPosition === camera.CameraPosition.CAMERA_POSITION_BACK ? + 60 : 30, // Obtain rate from video configuration }; // [End av_profile] @@ -288,15 +296,15 @@ export class VideoManager implements OutputManager { audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_CAMCORDER, videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV, profile: this.avProfile, - url: `fd://${this.file?.fd}`, + url: `fd://${this.file.fd}`, metadata: { videoOrientation: this.getCameraImageRotation().toString() } - } + }; // [End av_config] } - getRealData(data: sensor.GravityResponse): number { + getRealData(data: sensor.GravityResponse | sensor.AccelerometerResponse): number { let getDeviceDegree: number = 0; let x = data.x; let y = data.y; @@ -309,7 +317,7 @@ export class VideoManager implements OutputManager { // Calculate the inverse tangent value let sd: Decimal = Decimal.atan2(y, -x); // Convert radian values to angle values; - let sc: Decimal = Decimal.round(Number(sd) / 3.141592653589 * 180) + let sc: Decimal = Decimal.round(Number(sd) / 3.141592653589 * 180); // Adjust angle to be relative to vertical orientation getDeviceDegree = 90 - Number(sc); // Normalize angle to 0-360 degrees range @@ -336,19 +344,19 @@ export class VideoManager implements OutputManager { sensor.once(sensor.SensorId.GRAVITY, (data: sensor.GravityResponse) => { resolve(this.getRealData(data)); }); - }) + }); return promise; } else { const promise: Promise = new Promise((resolve) => { sensor.once(sensor.SensorId.ACCELEROMETER, (data: sensor.AccelerometerResponse) => { - resolve(this.getRealData(data as sensor.GravityResponse)); + resolve(this.getRealData(data)); }); - }) + }); return promise; } } catch (error) { - Logger.info(TAG_LOG, `Failed to getGravity and catch error is ${error.message}`); - return 0 + Logger.error(TAG_LOG, `Failed to getGravity and catch error is ${error.message}`); + return 0; } } @@ -356,22 +364,27 @@ export class VideoManager implements OutputManager { getVideoRotation(deviceDegree: number): camera.ImageRotation { let videoRotation: camera.ImageRotation = this.getCameraImageRotation(); try { - videoRotation = this.output!.getVideoRotation(deviceDegree); + if (this.output) { + videoRotation = this.output.getVideoRotation(deviceDegree); + } Logger.info(TAG_LOG, `Video rotation is: ${videoRotation}`); } catch (error) { - Logger.info(TAG_LOG, `Failed to getVideoRotation and catch error is: ${error.message}`); + Logger.error(TAG_LOG, `Failed to getVideoRotation and catch error is: ${error.message}`); } return videoRotation; } // [End get_video_rotation] - async getVideoThumbnail() { - let pixelMap: image.PixelMap | undefined = undefined; + async getVideoThumbnail(): Promise { + let pixelMap: image.PixelMap | undefined; + if (!this.file) { + return pixelMap; + } try { let avImageGenerator: media.AVImageGenerator = await media.createAVImageGenerator(); let dataSrc: media.AVFileDescriptor = { - fd: this.file!.fd, + fd: this.file.fd, }; avImageGenerator.fdSrc = dataSrc; let timeUs = 0; @@ -381,14 +394,16 @@ export class VideoManager implements OutputManager { height: 300 }; pixelMap = await avImageGenerator.fetchFrameByTime(timeUs, queryOption, param); - avImageGenerator.release(); + avImageGenerator.release().catch((err: BusinessError) => { + Logger.error(TAG_LOG, `avImageGenerator.release failed, code is ${err.code}, message is ${err.message}`); + }); } catch (error) { - Logger.info(TAG_LOG, `Failed to getVideoThumbnail and catch error is ${error.message}`); + Logger.error(TAG_LOG, `Failed to getVideoThumbnail and catch error is ${error.message}`); } return pixelMap; } - isRecording() { + isRecording(): boolean { return this.state === AVRecorderState.STARTED || this.state === AVRecorderState.PAUSED; } @@ -419,7 +434,7 @@ export class VideoManager implements OutputManager { // [End set_video_stabilization] // [Start set_video_color_space] - getSupportedColorSpaces(session: camera.VideoSession): Array { + getSupportedColorSpaces(session: camera.VideoSession): colorSpaceManager.ColorSpace[] { let colorSpaces: colorSpaceManager.ColorSpace[] = []; try { colorSpaces = session.getSupportedColorSpaces(); diff --git a/camera/src/main/ets/components/GridLine.ets b/camera/src/main/ets/components/GridLine.ets index 58ad0781d717a568d496ca7dd7f3816c08160361..f3e5b970cf47eaf21ae014265e05629fd655dc68 100644 --- a/camera/src/main/ets/components/GridLine.ets +++ b/camera/src/main/ets/components/GridLine.ets @@ -13,20 +13,20 @@ * limitations under the License. */ -@Component /** * Universal Camera Preview Grid Component. */ +@Component export struct GridLine { private settings: RenderingContextSettings = new RenderingContextSettings(true); private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings); @Prop cols: number = 3; @Prop rows: number = 3; - @Prop strokeStyle: string |number |CanvasGradient | CanvasPattern = Color.White; + @Prop strokeStyle: string | number | CanvasGradient | CanvasPattern = Color.White; @Prop lineWidth: number = 1; // [Start draw] - draw() { + draw(): void { const ctx = this.context; ctx.strokeStyle = this.strokeStyle; ctx.lineWidth = this.lineWidth; diff --git a/camera/src/main/ets/components/LevelIndicator.ets b/camera/src/main/ets/components/LevelIndicator.ets index c7c951266fbf7df127c8efddc5203841edade117..0d5ab892029cce3ccd9f380210811048f14b5261 100644 --- a/camera/src/main/ets/components/LevelIndicator.ets +++ b/camera/src/main/ets/components/LevelIndicator.ets @@ -20,15 +20,15 @@ import { Logger } from 'commons'; const ANGLE_DIFFERENCE: number = 3; const TAG = 'LevelIndicator'; -// [Start LevelIndicator] -@Component /** * Universal Camera Preview Level Component. */ +// [Start LevelIndicator] +@Component export struct LevelIndicator { @Prop acc: sensor.AccelerometerResponse; - getRotate() { + getRotate(): number { let displayDefault: display.Display | null = null; try { displayDefault = display.getDefaultDisplaySync(); @@ -42,9 +42,9 @@ export struct LevelIndicator { return -Math.atan2(-this.acc.x, this.acc.y) * (180 / Math.PI); } - isAlign() { - return Math.abs(this.getRotate()) - 0 <= ANGLE_DIFFERENCE - || Math.abs(Math.abs(this.getRotate()) - 90) <= ANGLE_DIFFERENCE; + isAlign(): boolean { + return Math.abs(this.getRotate()) - 0 <= ANGLE_DIFFERENCE || + Math.abs(Math.abs(this.getRotate()) - 90) <= ANGLE_DIFFERENCE; } build() { diff --git a/camera/src/main/ets/constants/CameraConstants.ets b/camera/src/main/ets/constants/CameraConstants.ets index 780f111cdc5b073fe0ef0f4c987da03823b69f21..25a47b53afc2ad37ca1fe98107326c6e64417ca5 100644 --- a/camera/src/main/ets/constants/CameraConstants.ets +++ b/camera/src/main/ets/constants/CameraConstants.ets @@ -14,7 +14,7 @@ */ class CameraConstant { - static readonly PROFILE_DIFFERENCE = 1e-10; + public static readonly PROFILE_DIFFERENCE = 1e-10; } export default CameraConstant; \ No newline at end of file diff --git a/entry/src/main/ets/constants/Constants.ets b/entry/src/main/ets/constants/Constants.ets index 63a319b6d0c08a541568b7e723e8fd367785b0b7..3954226cde14359119e1c6e4d917fc4734e7e8fe 100644 --- a/entry/src/main/ets/constants/Constants.ets +++ b/entry/src/main/ets/constants/Constants.ets @@ -13,12 +13,12 @@ * limitations under the License. */ -import { Permissions } from "@kit.AbilityKit"; +import { Permissions } from '@kit.AbilityKit'; class CameraConstant { - static readonly RATIO_PHOTO: number = 4 / 3; - static readonly RATIO_VIDEO: number = 16 / 9; - static readonly PERMISSIONS: Permissions[] = [ + public static readonly RATIO_PHOTO: number = 4 / 3; + public static readonly RATIO_VIDEO: number = 16 / 9; + public static readonly PERMISSIONS: Permissions[] = [ 'ohos.permission.CAMERA', 'ohos.permission.MICROPHONE', 'ohos.permission.MEDIA_LOCATION', diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets index 3c346dc45817df9f8653bded83b97e44641ddf9b..66b4d380585b33de1021e797b675e1f743e3add1 100644 --- a/entry/src/main/ets/entryability/EntryAbility.ets +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -46,7 +46,7 @@ export default class EntryAbility extends UIAbility { } windowStage.loadContent('pages/Index', (err) => { - if (err.code) { + if (err) { hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); return; } diff --git a/entry/src/main/ets/models/CameraManagerModel.ets b/entry/src/main/ets/models/CameraManagerModel.ets index 2854c9320fddd951bb1e069fd9a731f2c82ed8c3..2e5fca54e4b30605bbf8bba22671aacbb06cf54c 100644 --- a/entry/src/main/ets/models/CameraManagerModel.ets +++ b/entry/src/main/ets/models/CameraManagerModel.ets @@ -14,21 +14,21 @@ */ import { CameraManager, ImageReceiverManager, MetadataManager, - PhotoManager, PreviewManager, VideoManager } from "camera"; + PhotoManager, PreviewManager, VideoManager } from 'camera'; /** * Camera manager data class. */ export class CameraManagerModel { - previewManager: PreviewManager; - photoManager: PhotoManager; - videoManager: VideoManager; - imageReceiverManager: ImageReceiverManager; - metadataManager: MetadataManager; - cameraManager: CameraManager; + public previewManager: PreviewManager; + public photoManager: PhotoManager; + public videoManager: VideoManager; + public imageReceiverManager: ImageReceiverManager; + public metadataManager: MetadataManager; + public cameraManager: CameraManager; constructor(context: Context, previewManager: PreviewManager, photoManager: PhotoManager, videoManager: VideoManager, - imageReceiverManager: ImageReceiverManager, metadataManager: MetadataManager,) { + imageReceiverManager: ImageReceiverManager, metadataManager: MetadataManager) { this.previewManager = previewManager; this.photoManager = photoManager; this.videoManager = videoManager; diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index 8f0ae7bb7dd4737789676c1d402d777dd60e3a23..586da2306f0ecd719cb20383dc5909fc88a639e2 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -34,11 +34,9 @@ const TAG = 'Index'; @Component struct Index { @State previewVM: PreviewViewModel = new PreviewViewModel(this.getUIContext()); - @State isSleeping: boolean = false; - @State sleepTimer: RefreshableTimer | undefined = undefined; private context: Context = this.getUIContext().getHostContext()!; private applicationContext = this.context.getApplicationContext(); - private windowClass: window.Window | undefined = undefined; + private windowClass?: window.Window; async aboutToAppear() { // Add state listener when the page initializes. @@ -57,7 +55,7 @@ struct Index { // Monitor device fold state changes. try { display.on('foldStatusChange', () => { - this.onFoldStatusChange() + this.onFoldStatusChange(); }); } catch (exception) { Logger.error(TAG, `onFoldStatusChange failed, code is ${exception.code}, message is ${exception.message}`); @@ -65,12 +63,15 @@ struct Index { } aboutToDisappear(): void { + sensor.off(sensor.SensorId.GRAVITY); + this.previewVM.sleepTimer?.clear(); + this.applicationContext.off('applicationStateChange'); this.removeOrientationChangeEventListener(); } // [Start addGravityEventListener] // Add a gravity sensor listener callback function. - addGravityEventListener() { + addGravityEventListener(): void { try { sensor.on(sensor.SensorId.GRAVITY, (data) => { this.previewVM.acc = data; @@ -83,26 +84,26 @@ struct Index { // [End addGravityEventListener] // [Start initSleepTimer] - initSleepTimer() { - this.sleepTimer = new RefreshableTimer(() => { + initSleepTimer(): void { + this.previewVM.sleepTimer = new RefreshableTimer(() => { this.previewVM.openPreviewBlur(); - this.isSleeping = true; + this.previewVM.isSleeping = true; this.previewVM.cameraManagerRelease(); }, 30 * 1000); - this.sleepTimer.start(); + this.previewVM.sleepTimer.start(); const observer = this.getUIContext().getUIObserver(); observer.on('willClick', () => { - this.sleepTimer?.refresh(); + this.previewVM.sleepTimer?.refresh(); }); } // [End initSleepTimer] // [Start registerApplicationStateChange] - registerApplicationStateChange() { + registerApplicationStateChange(): void { this.applicationContext.on('applicationStateChange', { onApplicationForeground: async () => { - await this.startCamera(); + await this.previewVM.cameraManagerStart(); // [StartExclude registerApplicationStateChange] this.previewVM.syncButtonSettings(); // [EndExclude registerApplicationStateChange] @@ -113,44 +114,36 @@ struct Index { // [EndExclude registerApplicationStateChange] this.previewVM.cameraManagerRelease(); } - }) - } - - // open camera and start session. - async startCamera() { - const cameraPosition = this.previewVM.getCameraPosition(); - const sceneMode = this.previewVM.getSceneMode(); - await this.previewVM.cameraManagerStart(cameraPosition, sceneMode); + }); } - // [End registerApplicationStateChange] // Rotation state listener. - addOrientationChangeEventListener() { + addOrientationChangeEventListener(): void { this.windowClass?.on('windowSizeChange', () => { this.previewVM.setPreviewSize(); }); } - removeOrientationChangeEventListener() { - this.windowClass?.off('windowSizeChange', () => { this.previewVM.setPreviewSize(); }); + removeOrientationChangeEventListener(): void { + this.windowClass?.off('windowSizeChange'); } // Fold state listener callback. - async onFoldStatusChange() { + async onFoldStatusChange(): Promise { await this.previewVM.cameraManagerRelease(); - await this.startCamera(); + await this.previewVM.cameraManagerStart(); this.previewVM.syncButtonSettings(); } // [Start onMetadataObjectsAvailable] // Face detection information listener callback. - onMetadataObjectsAvailable(faceBoxArr: camera.Rect[]) { + onMetadataObjectsAvailable(faceBoxArr: camera.Rect[]): void { faceBoxArr.forEach((value) => { // Convert normalized coordinates to actual coordinates. value.topLeftX *= this.previewVM.getPreviewWidth(); value.topLeftY *= this.previewVM.getPreviewHeight(); value.width *= this.previewVM.getPreviewWidth(); value.height *= this.previewVM.getPreviewHeight(); - }) + }); this.previewVM.faceBoundingBoxArr = faceBoxArr; } // [End onMetadataObjectsAvailable] @@ -170,9 +163,9 @@ struct Index { .justifyContent(FlexAlign.Center) // [EndExclude wakeupMask] .onClick(async () => { - this.isSleeping = false; - this.sleepTimer?.refresh(); - await this.startCamera(); + this.previewVM.isSleeping = false; + this.previewVM.sleepTimer?.refresh(); + await this.previewVM.cameraManagerStart(); this.previewVM.syncButtonSettings(); }) } @@ -184,8 +177,7 @@ struct Index { if (this.previewVM.isPreviewManagerExist()) { // Display camera preview and related preview auxiliary components. PreviewScreenView({ - previewVM: this.previewVM, - sleepTimer: this.sleepTimer + previewVM: this.previewVM }); } @@ -204,7 +196,7 @@ struct Index { // Camera shooting settings button. SettingButtonsView({ previewVM: this.previewVM - }) + }); // Camera focal length setting button if (!this.previewVM.photoRemainder) { @@ -212,23 +204,23 @@ struct Index { !(this.previewVM.isStabilizationEnabled && this.previewVM.isVideoMode())) { ZoomButtonsView({ previewVM: this.previewVM - }) + }); } // Camera shooting mode setting button. ModeButtonsView({ previewVM: this.previewVM - }) + }); // Camera Shooting Operation Buttons. OperateButtonsView({ previewVM: this.previewVM - }) + }); } // Camera Sleep Prompt. - if (this.isSleeping) { - this.wakeupMask() + if (this.previewVM.isSleeping) { + this.wakeupMask(); } } .height('100%') diff --git a/entry/src/main/ets/utils/CommonUtil.ets b/entry/src/main/ets/utils/CommonUtil.ets index ae858af78e54d7cb5941163447f5d1262035838c..5a02db35e8dad8f222b4431cdb31de343e2b3eff 100644 --- a/entry/src/main/ets/utils/CommonUtil.ets +++ b/entry/src/main/ets/utils/CommonUtil.ets @@ -26,7 +26,7 @@ export interface LinePoint { increment: camera.Point } -export function limitNumberInRange(src: number, range: number[]) { +export function limitNumberInRange(src: number, range: number[]): number { if (range.length < 2) { return src; } @@ -41,7 +41,7 @@ export function limitNumberInRange(src: number, range: number[]) { // find start index the target in which range // eg: target: 1.5 arr: [0, 1, 5, 10] return 1 -export function findRangeIndex(target: number, arr: number[]) { +export function findRangeIndex(target: number, arr: number[]): number { if (arr.length === 0) { return -1; } @@ -56,7 +56,7 @@ export function findRangeIndex(target: number, arr: number[]) { // Math floor float by digit // eg: toFixed(9.97, 1) -> 9.9 export function toFixed(num: number, digit: number): string { - const scale = 10**digit; + const scale = 10 ** digit; return (Math.floor(num * scale) / scale).toFixed(digit); } @@ -102,7 +102,7 @@ export function calFaceBoxLinePoint(faceBoxRect: camera.Rect): LinePoint[] { // Calculate the relative coordinates of each edge. startPoints.forEach((startPoint: camera.Point) => { - let HorizontalLine: LinePoint = { + let horizontalLine: LinePoint = { start: startPoint, increment: { x: startPoint.x > faceBoxRect.topLeftX ? -lineLength : lineLength, y: 0 } }; @@ -112,20 +112,20 @@ export function calFaceBoxLinePoint(faceBoxRect: camera.Rect): LinePoint[] { increment: { x: 0, y: startPoint.y > faceBoxRect.topLeftY ? -lineLength : lineLength } }; - linePoints.push(HorizontalLine, verticalLine); + linePoints.push(horizontalLine, verticalLine); }); return linePoints; } // [End calFaceBoxLinePoint] export function showToast( - UIContext: UIContext, + curUIContext: UIContext, message: ResourceStr = '', duration = 2000, alignment = Alignment.Top, offset: Offset = { dx: 0, dy: 300 } -) { - UIContext.getPromptAction().openToast({ +): void { + curUIContext.getPromptAction().openToast({ message, duration, alignment, diff --git a/entry/src/main/ets/utils/PermissionManager.ets b/entry/src/main/ets/utils/PermissionManager.ets index 95de42ca8d4f1063e501950295a02931f005205a..319fdd260dd0f0b65ea2676c4fb5e924723da556 100644 --- a/entry/src/main/ets/utils/PermissionManager.ets +++ b/entry/src/main/ets/utils/PermissionManager.ets @@ -22,7 +22,7 @@ const TAG = 'PermissionManager'; class PermissionManager { private static atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); - static async request(permissions: Permissions[], context: Context) { + static async request(permissions: Permissions[], context: Context): Promise { try { const data = await PermissionManager.atManager.requestPermissionsFromUser(context, permissions); const grantStatus: number[] = data.authResults; @@ -36,6 +36,7 @@ class PermissionManager { } } catch (exception) { Logger.error(TAG, `request failed, code is ${exception.code}, message is ${exception.message}`); + throw new Error('permission failed'); } } } diff --git a/entry/src/main/ets/utils/RefreshableTimer.ets b/entry/src/main/ets/utils/RefreshableTimer.ets index 4956219f7c1c0ff6d3a80d1c46978f556ca02768..5dc48553744490ccdcc11044c5a24644764b1138 100644 --- a/entry/src/main/ets/utils/RefreshableTimer.ets +++ b/entry/src/main/ets/utils/RefreshableTimer.ets @@ -26,6 +26,7 @@ class RefreshableTimer { } start(): void { + clearTimeout(this.timerId); this.timerId = setTimeout(() => { this.callback(); this.isActive = false; diff --git a/entry/src/main/ets/utils/WindowUtil.ets b/entry/src/main/ets/utils/WindowUtil.ets index 0d9bc91690df90af78208f3c5b42f4799b155a8c..6b92c998f3a03faa747f157f8ccac0165cffe43a 100644 --- a/entry/src/main/ets/utils/WindowUtil.ets +++ b/entry/src/main/ets/utils/WindowUtil.ets @@ -16,7 +16,7 @@ import { display, window } from '@kit.ArkUI'; import { Logger } from 'commons'; -const TAG = 'WindowUtil' +const TAG = 'WindowUtil'; class WindowUtil { static async enterImmersive(window: window.Window): Promise { @@ -36,7 +36,6 @@ class WindowUtil { } catch (exception) { Logger.error(TAG, `getDefaultDisplaySync failed, code is ${exception.code}, message is ${exception.message}`); } - //const defaultDisplay: display.Display = display.getDefaultDisplaySync(); const windowWidth: number = defaultDisplay?.width ?? 0; const windowHeight: number = defaultDisplay?.height ?? 0; const rotation = (defaultDisplay?.rotation ?? 0) * 90; diff --git a/entry/src/main/ets/viewmodels/PreviewViewModel.ets b/entry/src/main/ets/viewmodels/PreviewViewModel.ets index 1e96aea1b479863bec4d96cef0c79b4db4d70ade..c89e6b0d215d88b43e9fc9ca9ad1a07d311d2f44 100644 --- a/entry/src/main/ets/viewmodels/PreviewViewModel.ets +++ b/entry/src/main/ets/viewmodels/PreviewViewModel.ets @@ -23,6 +23,7 @@ import { ImageReceiverManager, MetadataManager, PhotoManager, PreviewManager, Vi import WindowUtil from '../utils/WindowUtil'; import CameraConstant from '../constants/Constants'; import { CameraManagerModel } from '../models/CameraManagerModel'; +import RefreshableTimer from '../utils/RefreshableTimer'; export enum CameraMode { PHOTO, @@ -36,41 +37,66 @@ const TAG = 'PreviewViewModel'; */ @Observed class PreviewViewModel { - private context: Context | undefined; - private uiContext: UIContext; - private cameraManagerModel: CameraManagerModel; // [Start isFront] - isFront: boolean = false; + public isFront: boolean = false; // [StartExclude isFront] - cameraMode: CameraMode = CameraMode.PHOTO; - xComponentController: XComponentController = new XComponentController(); - surfaceId: string = ''; - previewSize: Size = WindowUtil.getMaxDisplaySize(CameraConstant.RATIO_PHOTO); - rates?: number[] = []; - currentRate: number = 0; - blurRadius: number = 0; - blurRotation: RotateOptions = { y: 0.5, angle: 0 }; - faceBoundingBoxArr: camera.Rect[] = []; + public cameraMode: CameraMode = CameraMode.PHOTO; + public xComponentController: XComponentController = new XComponentController(); + public surfaceId: string = ''; + public previewSize: Size = WindowUtil.getMaxDisplaySize(CameraConstant.RATIO_PHOTO); + public rates?: number[] = []; + public currentRate: number = 0; + public blurRadius: number = 0; + public blurRotation: RotateOptions = { y: 0.5, angle: 0 }; + public faceBoundingBoxArr: camera.Rect[] = []; // Accelerometer data. - acc: sensor.AccelerometerResponse = { x: 0, y: 0, z: 0 } as sensor.AccelerometerResponse; + public acc: sensor.AccelerometerResponse = { x: 0, y: 0, z: 0 } as sensor.AccelerometerResponse; // Timer photo settings params. - isDelayTakePhoto: boolean = false; - photoDelayTime: number = 0; - photoRemainder: number = 0; + public isDelayTakePhoto: boolean = false; + public photoDelayTime: number = 0; + public photoRemainder: number = 0; - isStabilizationEnabled: boolean = false; + public isStabilizationEnabled: boolean = false; // Focal length params. - zoomRange: number[] = []; - zooms: number[] = [1, 5, 10]; - currentZoom: number = 1; + public zoomRange: number[] = []; + public zooms: number[] = [1, 5, 10]; + public currentZoom: number = 1; + + public isGridLineVisible: boolean = false; + public isLevelIndicatorVisible: boolean = false; + public isSinglePhoto: boolean = false; + public isLivePhoto: boolean = false; + public isPreviewImageVisible: boolean = false; + public previewImage: PixelMap | ResourceStr = ''; + public isSleeping: boolean = false; + public sleepTimer: RefreshableTimer | undefined = undefined; + // [Start getProfile] + // Get the camera's width, height, and format params. + public getProfile: (cameraOrientation: number) => camera.Profile = cameraOrientation => { + const displaySize: Size = WindowUtil.getMaxDisplaySize(this.getPreviewRatio()); + let displayDefault: display.Display | null = null; + try { + displayDefault = display.getDefaultDisplaySync(); + } catch (exception) { + Logger.error(TAG, `getDefaultDisplaySync failed, code is ${exception.code}, message is ${exception.message}`); + } + // Get actual rotation angle. + const displayRotation = (displayDefault?.rotation ?? 0) * 90; + const isRevert = (cameraOrientation + displayRotation) % 180 !== 0; + return { + format: camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP, + size: { + height: isRevert ? displaySize.width : displaySize.height, + width: isRevert ? displaySize.height : displaySize.width + } + }; + }; + // [End getProfile] - isGridLineVisible: boolean = false; - isLevelIndicatorVisible: boolean = false; - isSinglePhoto: boolean = false; - isLivePhoto: boolean = false; - isPreviewImageVisible: boolean = false; - previewImage: PixelMap | ResourceStr = ''; + private context: Context | undefined; + private uiContext: UIContext; + private cameraManagerModel: CameraManagerModel; constructor(uiContext: UIContext) { this.uiContext = uiContext; @@ -90,35 +116,37 @@ class PreviewViewModel { this.onMetadataObjectsAvailable(faceBoxArr); }); this.cameraManagerModel = new CameraManagerModel(context, previewManager, - photoManager, videoManager, imageReceiverManager, metadataManager) + photoManager, videoManager, imageReceiverManager, metadataManager); } - onPreviewStart() { + onPreviewStart(): void { this.closePreviewBlur(); } - onImageReceiver(pixelMap: PixelMap) { + onImageReceiver(pixelMap: PixelMap): void { this.previewImage = pixelMap; } // [Start onMetadataObjectsAvailable] - onMetadataObjectsAvailable(faceBoxArr: camera.Rect[]) { + onMetadataObjectsAvailable(faceBoxArr: camera.Rect[]): void { faceBoxArr.forEach((value) => { // Convert normalized coordinates to actual coordinates. value.topLeftX *= this.getPreviewWidth(); value.topLeftY *= this.getPreviewHeight(); value.width *= this.getPreviewWidth(); value.height *= this.getPreviewHeight(); - }) + }); this.faceBoundingBoxArr = faceBoxArr; } // [End onMetadataObjectsAvailable] - async cameraManagerStart(cameraPosition: camera.CameraPosition, sceneMode: camera.SceneMode) { + async cameraManagerStart(): Promise { + let cameraPosition = this.getCameraPosition(); + let sceneMode = this.getSceneMode(); await this.cameraManagerModel.cameraManager.start(this.surfaceId, cameraPosition, sceneMode, this.getProfile); } - async cameraManagerRelease() { + async cameraManagerRelease(): Promise { await this.cameraManagerModel.cameraManager.release(); } @@ -126,34 +154,36 @@ class PreviewViewModel { return this.cameraManagerModel.cameraManager.getZoomRange(); } - setCameraZoomRatio() { + setCameraZoomRatio(): void { this.cameraManagerModel.cameraManager.setZoomRatio(this.currentZoom); } - setCameraFocusPoint(cameraPoint: camera.Point) { + setCameraFocusPoint(cameraPoint: camera.Point): void { this.cameraManagerModel.cameraManager.setFocusPoint(cameraPoint); } - setCameraMeteringPoint(cameraPoint: camera.Point) { + setCameraMeteringPoint(cameraPoint: camera.Point): void { this.cameraManagerModel.cameraManager.setMeteringPoint(cameraPoint); } - setCameraFlashMode(mode: camera.FlashMode) { + setCameraFlashMode(mode: camera.FlashMode): void { this.cameraManagerModel.cameraManager.setFlashMode(mode); } - setCameraVideoStabilizationMode(mode: camera.VideoStabilizationMode) { + setCameraVideoStabilizationMode(mode: camera.VideoStabilizationMode): void { this.cameraManagerModel.cameraManager.setVideoStabilizationMode(mode); } - setCameraSmoothZoom(zoom: number) { + setCameraSmoothZoom(zoom: number): void { this.cameraManagerModel.cameraManager.setSmoothZoom(zoom); } - syncButtonSettings() { + syncButtonSettings(): void { this.cameraManagerModel.previewManager.setFrameRate(this.currentRate, this.currentRate); this.cameraManagerModel.photoManager.enableMovingPhoto(this.isLivePhoto); this.cameraManagerModel.photoManager.setPhotoOutputCallback(this.isSinglePhoto); + this.currentZoom = 1; + this.faceBoundingBoxArr.length = 0; } isPreviewManagerExist(): boolean { @@ -163,39 +193,39 @@ class PreviewViewModel { return false; } - setPhotoIsActive() { + setPhotoIsActive(): void { this.cameraManagerModel.photoManager.setIsActive(this.isPhotoMode() ? true : false); } - setVideoIsActive() { + setVideoIsActive(): void { this.cameraManagerModel.videoManager.setIsActive(this.isPhotoMode() ? false : true); } - setPhotoCallback(cb: (pixelMap: image.PixelMap, url: string) => void) { + setPhotoCallback(cb: (pixelMap: image.PixelMap, url: string) => void): void { this.cameraManagerModel.photoManager.setCallback(cb); } - setVideoCallback(cb: (pixelMap: image.PixelMap, url: string) => void) { + setVideoCallback(cb: (pixelMap: image.PixelMap, url: string) => void): void { this.cameraManagerModel.videoManager.setVideoCallback(cb); } - async photoCapture() { + async photoCapture(): Promise { this.cameraManagerModel.photoManager.capture(this.isFront); } - async videoStart() { + async videoStart(): Promise { this.cameraManagerModel.videoManager.start(this.isFront); } - async videoPause() { + async videoPause(): Promise { this.cameraManagerModel.videoManager.pause(); } - async videoResume() { + async videoResume(): Promise { this.cameraManagerModel.videoManager.resume(); } - async videoStop() { + async videoStop(): Promise { await this.cameraManagerModel.videoManager.stop(); } @@ -211,65 +241,40 @@ class PreviewViewModel { return this.cameraManagerModel.previewManager.getSupportedFrameRates(); } - setPreviewFrameRate(minRate: number, maxRate: number) { + setPreviewFrameRate(minRate: number, maxRate: number): void { this.cameraManagerModel.previewManager.setFrameRate(minRate, maxRate); } - enableMovingPhoto() { + enableMovingPhoto(): void { this.cameraManagerModel.photoManager.enableMovingPhoto(this.isLivePhoto); } - setPhotoOutputCallback() { + setPhotoOutputCallback(): void { this.cameraManagerModel.photoManager.setPhotoOutputCallback(this.isSinglePhoto); } // [EndExclude isFront] - getCameraPosition() { + getCameraPosition(): camera.CameraPosition { return this.isFront ? camera.CameraPosition.CAMERA_POSITION_FRONT : camera.CameraPosition.CAMERA_POSITION_BACK; } - // [End isFront] - getPreviewRatio() { + getPreviewRatio(): number { return this.cameraMode === CameraMode.PHOTO ? CameraConstant.RATIO_PHOTO : CameraConstant.RATIO_VIDEO; } - getSceneMode() { + getSceneMode(): camera.SceneMode { return this.cameraMode === CameraMode.PHOTO ? camera.SceneMode.NORMAL_PHOTO : camera.SceneMode.NORMAL_VIDEO; } - // [Start getProfile] - // Get the camera's width, height, and format params. - getProfile: (cameraOrientation: number) => camera.Profile = cameraOrientation => { - const displaySize: Size = WindowUtil.getMaxDisplaySize(this.getPreviewRatio()); - let displayDefault: display.Display | null = null; - try { - displayDefault = display.getDefaultDisplaySync(); - } catch (exception) { - Logger.error(TAG, `getDefaultDisplaySync failed, code is ${exception.code}, message is ${exception.message}`); - } - // Get actual rotation angle. - const displayRotation = (displayDefault?.rotation ?? 0) * 90; - const isRevert = (cameraOrientation + displayRotation) % 180 !== 0; - return { - format: camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP, - size: { - height: isRevert ? displaySize.width : displaySize.height, - width: isRevert ? displaySize.height : displaySize.width - } - }; - } - - // [End getProfile] - // [Start setPreviewSize] - setPreviewSize() { + setPreviewSize(): void { const displaySize: Size = WindowUtil.getMaxDisplaySize(this.getPreviewRatio()); this.previewSize = displaySize; this.xComponentController.setXComponentSurfaceRect({ @@ -277,36 +282,35 @@ class PreviewViewModel { surfaceHeight: displaySize.height }); } - // [End setPreviewSize] - getPreviewTop() { + getPreviewTop(): number { const previewRatio = this.getPreviewRatio(); return WindowUtil.getWindowRatio() > previewRatio ? 85 : 0; } - getPreviewWidth() { + getPreviewWidth(): number { return this.uiContext.px2vp(this.previewSize.width); } - getPreviewHeight() { + getPreviewHeight(): number { return this.uiContext.px2vp(this.previewSize.height); } - isPhotoMode() { + isPhotoMode(): boolean { return this.cameraMode === CameraMode.PHOTO; } - isVideoMode() { + isVideoMode(): boolean { return this.cameraMode === CameraMode.VIDEO; } - isCurrentCameraMode(mode: CameraMode) { + isCurrentCameraMode(mode: CameraMode): boolean { return this.cameraMode === mode; } // open blur animation - openPreviewBlur() { + openPreviewBlur(): void { animateToImmediately({ duration: 200, curve: Curve.Friction @@ -316,7 +320,7 @@ class PreviewViewModel { } // Blur animation when rotating is enabled. - rotatePreviewBlur() { + rotatePreviewBlur(): void { animateToImmediately({ delay: 50, duration: 200, @@ -330,7 +334,7 @@ class PreviewViewModel { } // Enable blur animation at the end of rotation. - rotatePreviewBlurSecond() { + rotatePreviewBlurSecond(): void { this.blurRotation = { y: 0.5, angle: 270 }; animateToImmediately({ duration: 200, @@ -344,7 +348,7 @@ class PreviewViewModel { } // close blur animation. - closePreviewBlur() { + closePreviewBlur(): void { animateToImmediately({ duration: 200, curve: Curve.FastOutSlowIn diff --git a/entry/src/main/ets/views/ModeButtonsView.ets b/entry/src/main/ets/views/ModeButtonsView.ets index 7886c58ff0f8c875bad496da5b5cabc2a9d4bae2..f50b1e88b6edf752659c82d3fa742deffade8a33 100644 --- a/entry/src/main/ets/views/ModeButtonsView.ets +++ b/entry/src/main/ets/views/ModeButtonsView.ets @@ -56,16 +56,14 @@ struct ModeButtonsView { this.previewVM.openPreviewBlur(); this.previewVM.cameraMode = modeBtn.mode; this.previewVM.setPreviewSize(); - const sceneMode = this.previewVM.getSceneMode(); - const cameraPosition = this.previewVM.getCameraPosition(); await this.previewVM.cameraManagerRelease(); this.previewVM.setPhotoIsActive(); this.previewVM.setVideoIsActive(); - await this.previewVM.cameraManagerStart(cameraPosition, sceneMode); + await this.previewVM.cameraManagerStart(); this.previewVM.syncButtonSettings(); } }) - }, (modeBtn: CameraModeButton) => modeBtn.mode.toString()) + }, (modeBtn: CameraModeButton) => modeBtn.mode.toString()); } .id('modeButtonsView') .width('40%') diff --git a/entry/src/main/ets/views/OperateButtonsView.ets b/entry/src/main/ets/views/OperateButtonsView.ets index 17a25e4d7c6b4cbca505878623ba698f6c73d6de..7c63cf80a034a0fffa4c2d25b50b0f951c72030e 100644 --- a/entry/src/main/ets/views/OperateButtonsView.ets +++ b/entry/src/main/ets/views/OperateButtonsView.ets @@ -19,6 +19,7 @@ import { BusinessError } from '@kit.BasicServicesKit'; import { AVRecorderState } from 'camera'; import { Logger } from 'commons'; import PreviewViewModel from '../viewmodels/PreviewViewModel'; +import { inputConsumer, KeyCode } from '@kit.InputKit'; const TAG = 'OperateButtonsView'; @@ -35,13 +36,109 @@ struct OperateButtonsView { private photoDelayTimer: number = 0; private context = this.getUIContext().getHostContext() as common.UIAbilityContext; private setThumbnail: (pixelMap: image.PixelMap, url: string) => void = (pixelMap: image.PixelMap, url: string) => { - this.thumbnail = pixelMap - this.thumbnailUrl = url - } + this.thumbnail = pixelMap; + this.thumbnailUrl = url; + }; + private volumeUpCallBackFunc: () => void = () => {}; + private volumeDownCallBackFunc: () => void = () => {}; aboutToAppear(): void { this.previewVM.setPhotoCallback(this.setThumbnail); this.previewVM.setVideoCallback(this.setThumbnail); + + this.setVolumeKeyCallback(); + } + + aboutToDisappear(): void { + try { + inputConsumer.off('keyPressed'); + } catch (error) { + Logger.error(TAG, `inputConsumer off keyPressed failed, code is ${error.code}, message is ${error.message}`); + } + } + + setVolumeKeyCallback(): void { + let volumeUpOptions: inputConsumer.KeyPressedConfig = { + key: KeyCode.KEYCODE_VOLUME_UP, + action: 1, + isRepeat: false + }; + + let volumeDownOptions: inputConsumer.KeyPressedConfig = { + key: KeyCode.KEYCODE_VOLUME_DOWN, + action: 1, + isRepeat: false + }; + + this.volumeUpCallBackFunc = (): void => { + Logger.info(TAG, 'KEYCODE_VOLUME_UP'); + this.volumeKeyPressedFunc(); + }; + + this.volumeDownCallBackFunc = (): void => { + Logger.info(TAG, 'KEYCODE_VOLUME_DOWN'); + this.volumeKeyPressedFunc(); + }; + + try { + inputConsumer.on('keyPressed', volumeUpOptions, this.volumeUpCallBackFunc); + inputConsumer.on('keyPressed', volumeDownOptions, this.volumeDownCallBackFunc); + } catch (error) { + Logger.error(TAG, `inputConsumer on keyPressed failed, code is ${error.code}, message is ${error.message}`); + } + } + + async volumeKeyPressedFunc(): Promise { + if (this.previewVM.isSleeping) { + this.previewVM.isSleeping = false; + this.previewVM.sleepTimer?.refresh(); + await this.previewVM.cameraManagerStart(); + this.previewVM.syncButtonSettings(); + return; + } + + if (this.previewVM.isPhotoMode()) { + this.takePhoto(); + } else { + if (this.previewVM.isVideoRecording()) { + this.stopRecord(); + } else { + this.previewVM.videoStart(); + } + } + } + + takePhoto(): void { + if (this.previewVM.photoDelayTime) { + // Shooting logic in timer photo mode. + this.previewVM.isDelayTakePhoto = true; + this.previewVM.photoRemainder = this.previewVM.photoDelayTime; + this.photoDelayTimer = setInterval(() => { + this.previewVM.photoRemainder--; + if (this.previewVM.photoRemainder === 0) { + this.previewVM.photoCapture(); + this.captureClickFlag++; + this.previewVM.isDelayTakePhoto = false; + clearInterval(this.photoDelayTimer); + } + }, 1000); + } else { + // Shooting logic in normal photo mode. + this.previewVM.photoCapture(); + this.captureClickFlag++; + } + } + + async stopRecord(): Promise { + // Check if the current status allows recording. + if (this.previewVM.checkVideoState(AVRecorderState.STARTED) || + this.previewVM.checkVideoState(AVRecorderState.PAUSED)) { + await this.previewVM.videoStop(); + // Release and reopen the camera. + await this.previewVM.cameraManagerRelease(); + await this.previewVM.cameraManagerStart(); + this.previewVM.syncButtonSettings(); + } } @Builder @@ -65,24 +162,7 @@ struct OperateButtonsView { }) .justifyContent(FlexAlign.Center) .onClick(() => { - if (this.previewVM.photoDelayTime) { - // Shooting logic in timer photo mode. - this.previewVM.isDelayTakePhoto = true; - this.previewVM.photoRemainder = this.previewVM.photoDelayTime; - this.photoDelayTimer = setInterval(() => { - this.previewVM.photoRemainder--; - if (this.previewVM.photoRemainder === 0) { - this.previewVM.photoCapture(); - this.captureClickFlag++; - this.previewVM.isDelayTakePhoto = false; - clearTimeout(this.photoDelayTimer); - } - }, 1000) - } else { - // Shooting logic in normal photo mode. - this.previewVM.photoCapture(); - this.captureClickFlag++; - } + this.takePhoto(); }) } @@ -130,17 +210,7 @@ struct OperateButtonsView { }) .justifyContent(FlexAlign.Center) .onClick(async () => { - // Check if the current status allows recording. - if (this.previewVM.checkVideoState(AVRecorderState.STARTED) || - this.previewVM.checkVideoState(AVRecorderState.PAUSED)) { - await this.previewVM.videoStop(); - // Release and reopen the camera. - await this.previewVM.cameraManagerRelease(); - const cameraPosition = this.previewVM.getCameraPosition(); - const sceneMode = this.previewVM.getSceneMode(); - await this.previewVM.cameraManagerStart(cameraPosition, sceneMode); - this.previewVM.syncButtonSettings(); - } + this.stopRecord(); }) } @@ -211,7 +281,7 @@ struct OperateButtonsView { .symbolEffect(new ReplaceSymbolEffect(EffectScope.WHOLE), true) .onClick(async () => { this.previewVM.isDelayTakePhoto = false; - clearTimeout(this.photoDelayTimer); + clearInterval(this.photoDelayTimer); this.previewVM.photoRemainder = 0; }) } @@ -228,10 +298,8 @@ struct OperateButtonsView { this.previewVM.rotatePreviewBlur(); // [EndExclude toggleCameraPositionButton] this.previewVM.isFront = !this.previewVM.isFront; - const cameraPosition = this.previewVM.getCameraPosition(); - const sceneMode = this.previewVM.getSceneMode(); await this.previewVM.cameraManagerRelease(); - await this.previewVM.cameraManagerStart(cameraPosition, sceneMode); + await this.previewVM.cameraManagerStart(); // [StartExclude toggleCameraPositionButton] this.previewVM.syncButtonSettings(); // [EndExclude toggleCameraPositionButton] @@ -242,25 +310,25 @@ struct OperateButtonsView { build() { Row() { - this.thumbnailButton() + this.thumbnailButton(); // Different buttons are shown in photo and video modes. if (this.previewVM.isPhotoMode()) { - this.photoButton() + this.photoButton(); } else { if (this.previewVM.isVideoRecording()) { - this.videoStopButton() + this.videoStopButton(); } else { - this.videoStartButton() + this.videoStartButton(); } } // Different components are displayed for different states in video mode. if (!this.previewVM.isVideoRecording()) { - this.toggleCameraPositionButton() + this.toggleCameraPositionButton(); } if (this.previewVM.isVideoMode() && this.previewVM.checkVideoState(AVRecorderState.STARTED)) { - this.videoPauseButton() + this.videoPauseButton(); } else if (this.previewVM.isVideoMode() && this.previewVM.checkVideoState(AVRecorderState.PAUSED)) { - this.videoResumeButton() + this.videoResumeButton(); } } .justifyContent(FlexAlign.SpaceAround) diff --git a/entry/src/main/ets/views/PreviewImageView.ets b/entry/src/main/ets/views/PreviewImageView.ets index 3d732a98aca42245f6783b437a44005741be23be..b7b9953ecabb007054f2aeeb547dbac16d517788 100644 --- a/entry/src/main/ets/views/PreviewImageView.ets +++ b/entry/src/main/ets/views/PreviewImageView.ets @@ -18,6 +18,7 @@ import { Logger } from 'commons'; import PreviewViewModel from '../viewmodels/PreviewViewModel'; const TAG = 'PreviewImageView'; +const DEFAULT_IMAGE_HEIGHT: number = 80; /** * Dual preview image component, displaying the second preview stream. @@ -25,10 +26,10 @@ const TAG = 'PreviewImageView'; @Component export struct PreviewImageView { @ObjectLink previewVM: PreviewViewModel; - private PreviewImageHeight: number = 80; + private previewImageHeight: number = DEFAULT_IMAGE_HEIGHT; // Determine the preview image size based on the current device size and rotation state. - getPreviewImageWidth() { + getPreviewImageWidth(): number { let displayDefault: display.Display | null = null; try { displayDefault = display.getDefaultDisplaySync(); @@ -38,13 +39,13 @@ export struct PreviewImageView { const rotation = (displayDefault?.rotation ?? 0) * 90; const ratio = this.previewVM.getPreviewRatio(); const displayRatio = rotation === 90 || rotation === 270 ? 1 / ratio : ratio; - return this.PreviewImageHeight / displayRatio; + return this.previewImageHeight / displayRatio; } build() { Image(this.previewVM.previewImage) .width(this.getPreviewImageWidth()) - .height(this.PreviewImageHeight) + .height(this.previewImageHeight) .alignRules({ bottom: { anchor: '__container__', align: VerticalAlign.Bottom }, left: { anchor: '__container__', align: HorizontalAlign.Start } diff --git a/entry/src/main/ets/views/PreviewScreenView.ets b/entry/src/main/ets/views/PreviewScreenView.ets index 913996fbb7e935a3e769528a60393a04eefd262d..578ba7cbea5149b7dbf432f38dcdfde9be169a27 100644 --- a/entry/src/main/ets/views/PreviewScreenView.ets +++ b/entry/src/main/ets/views/PreviewScreenView.ets @@ -36,7 +36,6 @@ export struct PreviewScreenView { @State focusBoxPosition: Edges = { top: 0, left: 0 }; @State flashBlackOpacity: number = 1; @State isShowBlack: boolean = false; - @Link sleepTimer: RefreshableTimer | undefined; @ObjectLink previewVM: PreviewViewModel; @StorageLink('captureClick') @Watch('onCaptureClick') captureClickFlag: number = 0; private context: Context = this.getUIContext().getHostContext()!; @@ -48,14 +47,14 @@ export struct PreviewScreenView { private focusBoxSize: Size = { width: 80, height: 80 }; private exposureFontSize: number = 24; - exitApp() { + exitApp(): void { this.applicationContext.killAllProcesses().catch((err: BusinessError) => { Logger.error('showToast', `showToast failed, code is ${err.code}, message is ${err.message}`); }); } // Initialize the range of focal lengths that the current camera can be set to. - initZooms() { + initZooms(): void { const zoomRange = this.previewVM.getCameraZoomRange(); const minZoom = zoomRange[0]; this.previewVM.zoomRange = zoomRange; @@ -65,7 +64,7 @@ export struct PreviewScreenView { } // Initialize the range of preview stream frame rates that can be set for the current camera. - initRates() { + initRates(): void { const frameRates = this.previewVM.getPreviewSupportedFrameRates(); if (frameRates && frameRates[0]) { const minRate = frameRates[0].min; @@ -94,7 +93,7 @@ export struct PreviewScreenView { } // The screen flashes black when taking a photo. - flashBlackAnim() { + flashBlackAnim(): void { this.flashBlackOpacity = 1; this.isShowBlack = true; animateToImmediately({ @@ -106,7 +105,7 @@ export struct PreviewScreenView { } }, () => { this.flashBlackOpacity = 0; - }) + }); } onCaptureClick(): void { @@ -145,14 +144,14 @@ export struct PreviewScreenView { // [StartExclude XComponent] await PermissionManager.request(CameraConstant.PERMISSIONS, this.context) .catch(() => { - this.exitApp() + this.exitApp(); }); // [EndExclude XComponent] this.previewVM.surfaceId = this.previewVM.xComponentController.getXComponentSurfaceId(); this.previewVM.setPreviewSize(); this.previewVM.xComponentController.setXComponentSurfaceRotation({ lock: true }); // [StartExclude XComponent] - await this.previewVM.cameraManagerStart(this.previewVM.getCameraPosition(), this.previewVM.getSceneMode()); + await this.previewVM.cameraManagerStart(); this.initZooms(); this.initRates(); // [EndExclude XComponent] @@ -165,7 +164,7 @@ export struct PreviewScreenView { .onActionStart(() => { this.originZoomBeforePinch = this.previewVM.currentZoom; this.isZoomPinching = true; - this.sleepTimer?.refresh(); + this.previewVM.sleepTimer?.refresh(); }) .onActionUpdate((event: GestureEvent) => { if (this.previewVM.isVideoMode() && this.previewVM.isStabilizationEnabled) { @@ -200,13 +199,13 @@ export struct PreviewScreenView { }) // [EndExclude Stack] if (this.previewVM.isGridLineVisible) { - GridLine() + GridLine(); } // [StartExclude Stack] if (this.previewVM.isLevelIndicatorVisible) { LevelIndicator({ acc: this.previewVM.acc - }) + }); } // focus box if (this.isFocusBoxVisible) { diff --git a/entry/src/main/ets/views/SettingButtonsView.ets b/entry/src/main/ets/views/SettingButtonsView.ets index 75296d03c8dbd3d508dcd34a5b75f5f86a9318d4..9556a3859352dc8d876fc3f61941ba546858084e 100644 --- a/entry/src/main/ets/views/SettingButtonsView.ets +++ b/entry/src/main/ets/views/SettingButtonsView.ets @@ -69,7 +69,7 @@ struct SettingButtonsView { camera.FlashMode.FLASH_MODE_ALWAYS_OPEN ]; - getFlashItem(mode: camera.FlashMode) { + getFlashItem(mode: camera.FlashMode): FlashItem | undefined { return this.flashItems.find(item => item.mode === mode); } @@ -96,13 +96,14 @@ struct SettingButtonsView { videoTimerBuilder() { if (this.previewVM.isVideoRecording()) { Row({ space: 5 }) { - SymbolGlyph(this.previewVM.checkVideoState(AVRecorderState.STARTED)? $r('sys.symbol.record_circle') : - $r('sys.symbol.pause')) + SymbolGlyph(this.previewVM.checkVideoState(AVRecorderState.STARTED) ? $r('sys.symbol.record_circle') : + $r('sys.symbol.pause')) .fontSize(22) - .fontColor(this.previewVM.checkVideoState(AVRecorderState.STARTED)? [Color.Red, 'rgba(255,0,0,0)'] : + .fontColor(this.previewVM.checkVideoState(AVRecorderState.STARTED) ? [Color.Red, 'rgba(255,0,0,0)'] : [Color.White]) .renderingStrategy(SymbolRenderingStrategy.MULTIPLE_COLOR) - Text(this.previewVM.checkVideoState(AVRecorderState.STARTED)? $r('app.string.recording') : $r('app.string.paused')) + Text(this.previewVM.checkVideoState(AVRecorderState.STARTED) ? $r('app.string.recording') : + $r('app.string.paused')) .fontColor(Color.White) .fontSize(12) } @@ -165,7 +166,7 @@ struct SettingButtonsView { ['2s', 2], ['5s', 5], ['10s', 10] - ]) + ]); return Array.from(menuTextMap.keys()).map(text => { const time = menuTextMap.get(text); const menuElement: MenuElement = { @@ -187,8 +188,11 @@ struct SettingButtonsView { : $r('sys.symbol.motion_stabilization_slash')) .onClick(() => { this.previewVM.isStabilizationEnabled = !this.previewVM.isStabilizationEnabled; - this.previewVM.setCameraVideoStabilizationMode(camera.VideoStabilizationMode.AUTO); - const message = this.previewVM.isStabilizationEnabled ? $r('app.string.stabilization_enable') : $r('app.string.stabilization_disabled'); + this.previewVM.isStabilizationEnabled ? + this.previewVM.setCameraVideoStabilizationMode(camera.VideoStabilizationMode.HIGH) : + this.previewVM.setCameraVideoStabilizationMode(camera.VideoStabilizationMode.OFF); + const message = this.previewVM.isStabilizationEnabled ? $r('app.string.stabilization_enable') : + $r('app.string.stabilization_disabled'); showToast(this.getUIContext(), message); }) .fontSize(22) @@ -217,20 +221,20 @@ struct SettingButtonsView { build() { Row() { if (this.previewVM.isPhotoMode()) { - this.rateButton() - this.flashButton(this.photoFlashModes) - this.delayPhotoButton(this.getPhotoDelayTimeElements()) + this.rateButton(); + this.flashButton(this.photoFlashModes); + this.delayPhotoButton(this.getPhotoDelayTimeElements()); if (!this.previewVM.isSinglePhoto) { - this.livePhotoButton() + this.livePhotoButton(); } - this.togglePhotoModeButton() + this.togglePhotoModeButton(); } else { if (this.previewVM.isVideoRecording()) { - this.videoTimerBuilder() + this.videoTimerBuilder(); } else { - this.rateButton() - this.flashButton(this.videoFlashModes) - this.stabilizationButton() + this.rateButton(); + this.flashButton(this.videoFlashModes); + this.stabilizationButton(); } } } diff --git a/entry/src/main/ets/views/ZoomButtonsView.ets b/entry/src/main/ets/views/ZoomButtonsView.ets index d998a2a8ab0174712510243711f9f7559a88f7c0..9d7b9e73064fb91df7a1214306c198917dfc398a 100644 --- a/entry/src/main/ets/views/ZoomButtonsView.ets +++ b/entry/src/main/ets/views/ZoomButtonsView.ets @@ -57,7 +57,7 @@ struct ZoomButtonsView { this.previewVM.setCameraSmoothZoom(zoom); this.previewVM.currentZoom = zoom; }) - }, (zoom: number) => zoom.toString()) + }, (zoom: number) => zoom.toString()); } .margin({ bottom: 40 }) .alignRules({