diff --git a/README.en.md b/README.en.md new file mode 100644 index 0000000000000000000000000000000000000000..6c5bea60228df3ba57290438848820856f7aadbe --- /dev/null +++ b/README.en.md @@ -0,0 +1,85 @@ +## Custom Camera Implementation + +### 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. + +### Preview + +![](./screenshots/devices/camera_en.png) + +How to use: + +1. After launching the application and completing authorization, the preview UI is displayed. +2. The buttons from left to right at the top of the screen are as follows: preview frame rate setting, flash setting, delayed shooting mode setting, motion photo mode setting, and single/dual-stage shutter mode setting. (The single-stage shutter mode does not support motion photo shooting.) +3. When switched to video recording mode, the top buttons from left to right are: preview frame rate setting, flash setting and image stabilization mode setting. +4. On the right side, the buttons are grid line, level, and dual-channel preview in order. +5. At the bottom, the buttons can be used to take photos, record videos, and switch between the front and rear cameras. + +### Project Directory + +``` +├──camera/src/ +│ ├──main/ets/ +│ │ ├──components +│ │ │ ├──GridLine.ets // Grid line component. +│ │ │ └──LevelIndicator.ets // Level indicator component. +│ │ ├──constants +│ │ │ └──CameraConstants.ets // Constant file. +│ │ └──cameraManagers +│ │ ├──CamaraManager.ets // Camera session management class. +│ │ ├──ImageReceiverManager.ets // ImageReceiver preview stream. +│ │ ├──MetadataManager.ets // Metadata output stream. +│ │ ├──OutputManager.ets // Output stream management abstract. +│ │ ├──PhotoManager.ets // Photo stream management class. +│ │ ├──VideoManager.ets // Video stream management class. +│ │ └──PreviewManager.ets // Preview stream management class. +│ └──Index.ets // Exported file of the camera module. +├──commons/src/main/ets/ +│ └──utils +│ └──Logger.ets // Log class. +├──entry/src/main/ets/ +│ ├──entryability +│ │ └──EntryAbility.ets // Entry point class. +│ ├──constants +│ │ └──Constants.ets // Constant file. +│ ├──pages +│ │ └──Index.ets // Entry preview page. +│ ├──views +│ │ ├──ModeButtonsView.ets // Photo mode switch button view. +│ │ ├──OperateButtonsView.ets // Operation button view. +│ │ ├──SettingButtonsView.ets // Setting button view. +│ │ └──ZoomButtonsView.ets // Zoom control button view. +│ ├──viewModels +│ │ └──PreviewViewModel.ets // Preview-related state management. +│ └──utils +│ ├──CommonUtil.ets // Common utility function module. +│ ├──PermissionManager.ets // Permission management class. +│ ├──RefreshableTimer.ets // Timer management class. +│ └──WindowUtil.ets // Window utility class. +└──entry/src/main/resources // Static resources. +``` + +### How to Implement + +1. Use Camera Kit capabilities. + +### Required Permissions + +- ohos.permission.CAMERA: required for camera operations. +- ohos.permission.MICROPHONE: required to access the microphone for video recording. +- ohos.permission.MEDIA_LOCATION: required to obtain geographical information. +- ohos.permission.WRITE_IMAGEVIDEO: required to write media files. +- ohos.permission.READ_IMAGEVIDEO: required to read media files. +- ohos.permission.APPROXIMATELY_LOCATION: required to obtain approximate location information of the device. +- ohos.permission.ACCELEROMETER: required for the acceleration sensor. + +### Constraints + +1. The sample is only supported on Huawei phones and tablets running standard systems. + +2. HarmonyOS: HarmonyOS 5.1.1 Release or later. + +3. DevEco Studio: DevEco Studio 5.1.1 Release or later. + +4. HarmonyOS SDK: HarmonyOS 5.1.1 Release SDK or later. diff --git a/README.md b/README.md index 35514f95ad5dcc56653ebf74b339e244dfe5f87f..227744da5ad00da182a8320d5c58a30472722d59 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,17 @@ ### 介绍 -本示例基于Camera Kit相机服务,实现基础预览、预览画面调整(前后置镜头切换、闪光灯、对焦、调焦等)、预览进阶功能(网格线、水平仪、超时暂停等)、双路预览、拍照(动图拍摄、延迟拍摄等)、录像等核心功能。为开发者提供自定义相机开发的完整参考与实践指导。 +本示例基于Camera Kit相机服务,使用ArkTS API实现基础预览、预览画面调整(前后置镜头切换、闪光灯、对焦、调焦、设置曝光中心点等)、预览进阶功能(网格线、水平仪、人脸检测、超时暂停等)、双路预览(获取预览帧数据)、拍照(动图拍摄、延迟拍摄等)、录像等核心功能。为开发者提供自定义相机开发的完整参考与实践指导。 ### 效果预览 -| 拍照 & 预览 | 录像 & 预览 | -|--------------------------------------|--------------------------------------| -| ![](./screenshots/devices/photo.png) | ![](./screenshots/devices/video.png) | - +![](./screenshots/devices/camera.png) 使用说明: 1. 打开应用,授权后展示预览界面。 -2. 上方从左至右按钮功能依次为:闪光灯设置、延迟拍照模式设置、动态拍照模式设置、单双段拍照模式设置(单段拍照模式不支持动态拍摄)。 -3. 切换录像模式,上方按钮依次为:闪关灯设置、防抖模式设置。 -4. 右侧按钮依次为:网格线、水平仪、双路预览。 +2. 上方从左至右按钮功能依次为:预览帧率设置、闪光灯设置、延迟拍照模式设置、动态拍照模式设置、单双段拍照模式设置(单段拍照模式不支持动态拍摄)。 +3. 切换录像模式,上方按钮依次为:预览帧率设置、闪关灯设置、防抖模式设置(模式不支持变焦)。 +4. 右侧按钮依次为:网格线、水平仪、双路预览(获取预览帧数据)。 5. 下方按钮可拍照,录像,切换前后置摄像头。 ### 工程目录 @@ -31,6 +28,7 @@ │ │ └──cameraManagers │ │ ├──CamaraManager.ets // 相机会话管理类 │ │ ├──ImageReceiverManager.ets // ImageReceiver预览流管理类 +│ │ ├──MetadataManager.ets // 元数据输出流管理类 │ │ ├──OutputManager.ets // 输出流管理类抽象接口 │ │ ├──PhotoManager.ets // 拍照流管理类 │ │ ├──VideoManager.ets // 视频流管理类 @@ -71,7 +69,8 @@ - ohos.permission.MICROPHONE:麦克风权限,用于录像 - ohos.permission.MEDIA_LOCATION: 用于获取地理信息 - ohos.permission.WRITE_IMAGEVIDEO:用于写入媒体文件 -- hos.permission.READ_IMAGEVIDEO:用于读取媒体文件 +- ohos.permission.READ_IMAGEVIDEO:用于读取媒体文件 +- ohos.permission.APPROXIMATELY_LOCATION:用于获取设备模糊位置信息 - ohos.permission.ACCELEROMETER:用于加速度传感器 diff --git a/camera/Index.ets b/camera/Index.ets index ccfbb991d25157b984dd7911b8e1bcb8dffb967b..c8eba2c9439e42abdab6e3b659c54a9ee2ecd56e 100644 --- a/camera/Index.ets +++ b/camera/Index.ets @@ -18,5 +18,6 @@ export { PreviewManager } from './src/main/ets/cameramanagers/PreviewManager'; export { PhotoManager } from './src/main/ets/cameramanagers/PhotoManager'; export { VideoManager, AVRecorderState } from './src/main/ets/cameramanagers/VideoManager'; export { ImageReceiverManager } from './src/main/ets/cameramanagers/ImageReceiverManager'; +export { MetadataManager } from './src/main/ets/cameramanagers/MetadataManager'; export { GridLine } from './src/main/ets/components/GridLine'; export { LevelIndicator } from './src/main/ets/components/LevelIndicator'; diff --git a/camera/oh-package-lock.json5 b/camera/oh-package-lock.json5 deleted file mode 100644 index ebb8b315c3eb0c6696e7e8e12bd9aaab4c3dd2d2..0000000000000000000000000000000000000000 --- a/camera/oh-package-lock.json5 +++ /dev/null @@ -1,19 +0,0 @@ -{ - "meta": { - "stableOrder": true, - "enableUnifiedLockfile": false - }, - "lockfileVersion": 3, - "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", - "specifiers": { - "commons@../commons": "commons@../commons" - }, - "packages": { - "commons@../commons": { - "name": "commons", - "version": "1.0.0", - "resolved": "../commons", - "registryType": "local" - } - } -} \ No newline at end of file diff --git a/camera/src/main/ets/cameramanagers/CameraManager.ets b/camera/src/main/ets/cameramanagers/CameraManager.ets index 942e6a50eeb6a3d00c9ce8aa24bb4d7261fa503c..34778d1467287c0118f538c62d5594c47f4bc564 100644 --- a/camera/src/main/ets/cameramanagers/CameraManager.ets +++ b/camera/src/main/ets/cameramanagers/CameraManager.ets @@ -14,30 +14,34 @@ */ import { camera } from '@kit.CameraKit'; -import { Logger } from 'commons/src/main/ets/utils/Logger'; import { BusinessError } from '@kit.BasicServicesKit'; +import { Logger } from 'commons'; import OutputManager, { CreateOutputConfig } from './OutputManager'; const TAG = 'CameraManager'; export class CameraManager { - private cameraManager: camera.CameraManager; + private cameraManager?: camera.CameraManager; session?: camera.PhotoSession | camera.VideoSession; private cameraInput?: camera.CameraInput; private outputManagers: OutputManager[] = []; constructor(context: Context, outputManagers: OutputManager[]) { - // [Start cameraManager] - this.cameraManager = camera.getCameraManager(context); - // [End cameraManager] - this.outputManagers = outputManagers; - this.addCameraStatusListener(); + try { + // [Start cameraManager] + this.cameraManager = camera.getCameraManager(context); + // [End cameraManager] + this.outputManagers = outputManagers; + this.addCameraStatusListener(); + } catch (exception) { + Logger.error(TAG, `constructor failed, code is ${exception.code}, message is ${exception.message}`); + } } addCameraStatusListener() { - this.cameraManager.on('cameraStatus', (err: BusinessError, statusInfo: camera.CameraStatusInfo) => { - if (err && err.code) { - Logger.error(TAG, 'cameraStatus with errorCode = ' + err.code); + this.cameraManager?.on('cameraStatus', (err: BusinessError, statusInfo: camera.CameraStatusInfo) => { + if (err && err.message) { + Logger.error(TAG, 'cameraStatus with errorMessage = ' + err.message); return; } Logger.info(TAG, `cameraStatusInfo: camera is ${statusInfo.camera.cameraId}, status is ${statusInfo.status}`); @@ -60,13 +64,13 @@ export class CameraManager { return; } // [Start cameraInput] - this.cameraInput = this.cameraManager.createCameraInput(device); - await this.cameraInput.open(); + this.cameraInput = this.cameraManager?.createCameraInput(device); + await this.cameraInput?.open(); // [End cameraInput] // [Start session] - const session = this.cameraManager.createSession(sceneMode); - session.beginConfig(); - session.addInput(this.cameraInput); + const session = this.cameraManager?.createSession(sceneMode); + session?.beginConfig(); + session?.addInput(this.cameraInput); // [StartExclude session] const config: CreateOutputConfig = { cameraManager: this.cameraManager, @@ -76,59 +80,82 @@ export class CameraManager { surfaceId: xComponentSurfaceId }; // [EndExclude session] + // [Start addOutput] for (const outputManager of this.outputManagers) { if (outputManager.isActive) { const output = await outputManager.createOutput(config); - session.addOutput(output); + session?.addOutput(output); } - }; - await session.commitConfig(); - await session.start(); + } + await session?.commitConfig(); + await session?.start(); + // [End addOutput] // [End session] this.session = session as (camera.PhotoSession | camera.VideoSession); this.setFocusMode(camera.FocusMode.FOCUS_MODE_AUTO); + this.setExposureMode(camera.ExposureMode.EXPOSURE_MODE_AUTO); } catch (e) { Logger.error(TAG, `Failed to start camera session. Cause ${JSON.stringify(e)}`); } } async refreshOutput(oldOutput: camera.CameraOutput, newOutput: camera.CameraOutput) { - await this.session?.stop(); - this.session?.beginConfig(); - this.session?.removeOutput(oldOutput); - this.session?.addOutput(newOutput); - await this.session?.commitConfig(); - await this.session?.start(); + try { + await this.session?.stop(); + this.session?.beginConfig(); + this.session?.removeOutput(oldOutput); + this.session?.addOutput(newOutput); + await this.session?.commitConfig(); + await this.session?.start(); + } catch (exception) { + Logger.error(TAG, `refreshOutput failed, code is ${exception.code}, message is ${exception.message}`); + } } // [Start release] async release() { - await this.session?.stop(); - for (const outputManager of this.outputManagers) { - if (outputManager.isActive) { - await outputManager.release(); + try { + await this.session?.stop(); + for (const outputManager of this.outputManagers) { + if (outputManager.isActive) { + await outputManager.release(); + } } - }; - await this.cameraInput?.close(); - await this.session?.release(); + await this.cameraInput?.close(); + await this.session?.release(); + } catch (exception) { + Logger.error(TAG, `release failed, code is ${exception.code}, message is ${exception.message}`); + } } + // [End release] // [Start getCameraDevice] getCameraDevice(cameraPosition: camera.CameraPosition) { - const cameraDevices = this.cameraManager.getSupportedCameras(); + const cameraDevices = this.cameraManager?.getSupportedCameras(); + if (!cameraDevices) { + Logger.error(TAG, `Failed to get camera device. cameraPosition: ${cameraPosition}}`); + return; + } const device = cameraDevices?.find(device => device.cameraPosition === cameraPosition) || cameraDevices[0]; if (!device) { Logger.error(TAG, `Failed to get camera device. cameraPosition: ${cameraPosition}}`); } return device; } + // [End getCameraDevice] // [Start getZoomRange] getZoomRange() { - return this.session!.getZoomRatioRange(); + try { + return this.session!.getZoomRatioRange(); + } catch (exception) { + Logger.error(TAG, `getZoomRange failed, code is ${exception.code}, message is ${exception.message}`); + return []; + } } + // [End getZoomRange] // [Start setFocusMode] @@ -140,26 +167,55 @@ export class CameraManager { return; } this.session?.setFocusMode(focusMode); - } catch(e) { + } catch (e) { Logger.error(TAG, 'setFocusMode error ' + JSON.stringify(e)); } } + // [End setFocusMode] // [Start setFocusPoint] setFocusPoint(point: camera.Point) { try { this.session?.setFocusPoint(point); - } catch(e) { + } catch (e) { Logger.error(TAG, 'setFocusPoint error ' + JSON.stringify(e)); } } + // [End setFocusPoint] + // [Start setExposureMode] + setExposureMode(exposureMode: camera.ExposureMode) { + try { + const isSupported = this.session?.isExposureModeSupported(exposureMode); + if (!isSupported) { + Logger.error(TAG, `setExposureMode error: focus mode ${exposureMode} is not supported`); + return; + } + this.session?.setExposureMode(exposureMode); + } catch (e) { + Logger.error(TAG, 'setExposureMode error ' + JSON.stringify(e)); + } + } + + // [End setExposureMode] + + // [Start setMeteringPoint] + setMeteringPoint(point: camera.Point) { + try { + this.session?.setMeteringPoint(point); + } catch (e) { + Logger.error(TAG, 'setMeteringPoint error ' + JSON.stringify(e)); + } + } + + // [End setMeteringPoint] + setZoomRatio(zoom: number) { try { this.session?.setZoomRatio(zoom); - } catch(e) { + } catch (e) { Logger.error(TAG, 'setZoomRatio error ' + JSON.stringify(e)); } } @@ -168,10 +224,11 @@ export class CameraManager { setSmoothZoom(zoom: number) { try { this.session?.setSmoothZoom(zoom); - } catch(e) { + } catch (e) { Logger.error(TAG, 'setSmoothZoom error ' + JSON.stringify(e)); } } + // [End setSmoothZoom] // [Start setFlashMode] @@ -183,10 +240,11 @@ export class CameraManager { return; } this.session?.setFlashMode(flashMode); - } catch(e) { + } catch (e) { Logger.error(TAG, 'setFlashMode error ' + JSON.stringify(e)); } } + // [End setFlashMode] setVideoStabilizationMode(stabilizationMode: camera.VideoStabilizationMode) { diff --git a/camera/src/main/ets/cameramanagers/ImageReceiverManager.ets b/camera/src/main/ets/cameramanagers/ImageReceiverManager.ets index c6943617cf3386cf7340fa7e2e6d80f3e9204bf6..556b7c344fdef60d631ca5e93a07343e5a676ddd 100644 --- a/camera/src/main/ets/cameramanagers/ImageReceiverManager.ets +++ b/camera/src/main/ets/cameramanagers/ImageReceiverManager.ets @@ -15,10 +15,10 @@ import { image } from '@kit.ImageKit'; import { camera } from '@kit.CameraKit'; +import { display } from '@kit.ArkUI'; import { BusinessError } from '@kit.BasicServicesKit'; -import { Logger } from 'commons/src/main/ets/utils/Logger'; +import { Logger } from 'commons'; import OutputManager, { CreateOutputConfig } from './OutputManager'; -import { display } from '@kit.ArkUI'; import CameraConstant from '../constants/CameraConstants'; const TAG = 'ImageReceiverManager'; @@ -34,10 +34,10 @@ export class ImageReceiverManager implements OutputManager { } async createOutput(config: CreateOutputConfig) { - const cameraOutputCap = config.cameraManager.getSupportedOutputCapability(config.device, config.sceneMode); + 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; - const previewProfile = cameraOutputCap.previewProfiles + const previewProfile = cameraOutputCap?.previewProfiles .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; @@ -49,13 +49,21 @@ export class ImageReceiverManager implements OutputManager { return; } const surfaceId = await this.init(config.profile.size); - this.output = config.cameraManager.createPreviewOutput(previewProfile, surfaceId); + try { + this.output = config.cameraManager?.createPreviewOutput(previewProfile, surfaceId); + } catch (exception) { + Logger.error(TAG, `createPreviewOutput failed, code is ${exception.code}, message is ${exception.message}`); + } this.position = config.device.cameraPosition; return this.output; } async release() { - await this.output?.release(); + try { + await this.output?.release(); + } catch (exception) { + Logger.error(TAG, `release failed, code is ${exception.code}, message is ${exception.message}`); + } this.output = undefined; } @@ -66,6 +74,7 @@ export class ImageReceiverManager implements OutputManager { this.onImageArrival(receiver); return surfaceId; } + // [End init] // [Start getPixelMap] @@ -87,6 +96,7 @@ export class ImageReceiverManager implements OutputManager { srcPixelFormat: image.PixelMapFormat.NV21, }); } + // [End getPixelMap] // [Start onImageArrival] @@ -112,7 +122,13 @@ export class ImageReceiverManager implements OutputManager { const stride = imgComponent.rowStride; Logger.info(TAG, `getComponent with width:${width} height:${height} stride:${stride}`); const pixelMap = await this.getPixelMap(imgComponent, width, height, stride); - const displayRotation = display.getDefaultDisplaySync().rotation * camera.ImageRotation.ROTATION_90; + 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) { @@ -142,5 +158,6 @@ export class ImageReceiverManager implements OutputManager { }); }); } + // [End onImageArrival] } diff --git a/camera/src/main/ets/cameramanagers/MetadataManager.ets b/camera/src/main/ets/cameramanagers/MetadataManager.ets new file mode 100644 index 0000000000000000000000000000000000000000..e09d55ea05d36a9f792c2aa94720a28fd7079300 --- /dev/null +++ b/camera/src/main/ets/cameramanagers/MetadataManager.ets @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 ("the License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { BusinessError } from '@kit.BasicServicesKit'; +import { camera } from '@kit.CameraKit'; +import { Logger } from 'commons'; +import OutputManager, { CreateOutputConfig } from './OutputManager'; + +const TAG_LOG = 'MetadataManager'; + +export class MetadataManager implements OutputManager { + output?: camera.MetadataOutput; + isActive: boolean = true; + onMetadataObjectsAvailable: (faceBoxArr: camera.Rect[]) => void; + + constructor(onMetadataObjectsAvailable: (faceBoxArr: camera.Rect[]) => void) { + this.onMetadataObjectsAvailable = onMetadataObjectsAvailable; + } + + // [Start MetadataOutput] + async createOutput(config: CreateOutputConfig): Promise { + const cameraOutputCap = config.cameraManager?.getSupportedOutputCapability(config.device, config.sceneMode); + if (!cameraOutputCap) { + Logger.error(TAG_LOG, 'Failed to get supported output capability.'); + return; + } + let metadataObjectTypes: Array = cameraOutputCap!.supportedMetadataObjectTypes; + try { + this.output = config.cameraManager?.createMetadataOutput(metadataObjectTypes); + if (this.output) { + this.addOutputListener(this.output); + } + } catch (error) { + Logger.error(TAG_LOG, `Failed to createMetadataOutput, error code: ${error.code}`); + } + return this.output; + } + // [End MetadataOutput] + + addOutputListener(output: camera.MetadataOutput): void { + this.addMetadataObjectsAvailableListener(output); + this.addMetadataErrorListener(output); + } + + // [Start metadataObjectsAvailable] + addMetadataObjectsAvailableListener(metadataOutput: camera.MetadataOutput): void { + metadataOutput.on('metadataObjectsAvailable', + (err: BusinessError, metadataObjectArr: Array) => { + if (err && err.code !== 0) { + Logger.error(TAG_LOG, `Metadata output on metadataObjectsAvailable error code: ${err.code}`); + return; + } + let boxRectArr: camera.Rect[] = []; + metadataObjectArr.forEach((obj: camera.MetadataObject)=>{ + boxRectArr.push(obj.boundingBox); + }); + this.onMetadataObjectsAvailable(boxRectArr); + }); + } + // [End metadataObjectsAvailable] + + addMetadataErrorListener(metadataOutput: camera.MetadataOutput): void { + metadataOutput.on('error', (metadataOutputError: BusinessError) => { + Logger.error(TAG_LOG, `Metadata output error code: ${metadataOutputError.code}`); + }); + } + + async release() { + try { + await this.output?.release(); + } catch (exception) { + Logger.error(TAG_LOG, `release failed, code is ${exception.code}, message is ${exception.message}`); + } + this.output = undefined; + } +} \ No newline at end of file diff --git a/camera/src/main/ets/cameramanagers/OutputManager.ets b/camera/src/main/ets/cameramanagers/OutputManager.ets index c476ae134b6c41de7df7b61c0cd47eb2700dfd2b..cf636eefe9ff43a033af25be5ac5b9717507d6b4 100644 --- a/camera/src/main/ets/cameramanagers/OutputManager.ets +++ b/camera/src/main/ets/cameramanagers/OutputManager.ets @@ -16,7 +16,7 @@ import { camera } from '@kit.CameraKit'; export interface CreateOutputConfig { - cameraManager: camera.CameraManager; + cameraManager?: camera.CameraManager; device: camera.CameraDevice; profile: camera.Profile; sceneMode?: camera.SceneMode; diff --git a/camera/src/main/ets/cameramanagers/PhotoManager.ets b/camera/src/main/ets/cameramanagers/PhotoManager.ets index 4b94383960f5c15880839d9be355e7c36e1f6149..b8a64d822efac6d86ed2385376a8b79688e56b89 100644 --- a/camera/src/main/ets/cameramanagers/PhotoManager.ets +++ b/camera/src/main/ets/cameramanagers/PhotoManager.ets @@ -19,11 +19,11 @@ import { photoAccessHelper } from '@kit.MediaLibraryKit'; import { sensor } from '@kit.SensorServiceKit'; import { Decimal } from '@kit.ArkTS'; import { image } from '@kit.ImageKit'; -import OutputManager, { CreateOutputConfig } from './OutputManager'; -import CameraConstant from '../constants/CameraConstants'; import { colorSpaceManager } from '@kit.ArkGraphics2D'; import { geoLocationManager } from '@kit.LocationKit'; -import { Logger } from 'commons/src/main/ets/utils/Logger'; +import { Logger } from 'commons'; +import OutputManager, { CreateOutputConfig } from './OutputManager'; +import CameraConstant from '../constants/CameraConstants'; const TAG_LOG = 'PhotoManager'; @@ -61,32 +61,31 @@ export class PhotoManager implements OutputManager { } // [Start create_photo_output] - public createPhotoOutput(cameraManager: camera.CameraManager, cameraDevice: camera.CameraDevice, + // Create photo output + public createPhotoOutput(cameraManager: camera.CameraManager|undefined, cameraDevice: camera.CameraDevice, profile: camera.Profile) { - const PROFILE_DIFFERENCE = 1e-10; let cameraPhotoOutput: camera.PhotoOutput | undefined = undefined; const cameraOutputCapability = - cameraManager.getSupportedOutputCapability(cameraDevice, camera.SceneMode.NORMAL_PHOTO); - let photoProfilesArray: Array | undefined = cameraOutputCapability?.photoProfiles; + cameraManager?.getSupportedOutputCapability(cameraDevice, camera.SceneMode.NORMAL_PHOTO); + let photoProfilesArray: camera.Profile[] | undefined = cameraOutputCapability?.photoProfiles; if (photoProfilesArray?.length) { try { const displayRatio = profile.size.width / profile.size.height; const profileWidth = profile.size.width; - const PhotoProfile = photoProfilesArray + const photoProfile = photoProfilesArray .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 Math.abs(pfDisplayRatio - displayRatio) <= PROFILE_DIFFERENCE && + return Math.abs(pfDisplayRatio - displayRatio) <= CameraConstant.PROFILE_DIFFERENCE && pf.format === camera.CameraFormat.CAMERA_FORMAT_JPEG; }); - if (!PhotoProfile) { + if (!photoProfile) { Logger.error(TAG_LOG, 'Failed to get photo profile'); return; } - cameraPhotoOutput = cameraManager.createPhotoOutput(PhotoProfile); + cameraPhotoOutput = cameraManager?.createPhotoOutput(photoProfile); } catch (error) { - let err = error as BusinessError; - Logger.error(TAG_LOG, `Failed to createPhotoOutput. error: ${JSON.stringify(err)}`); + Logger.error(TAG_LOG, `Failed to createPhotoOutput. error: ${JSON.stringify(error)}`); } } this.output = cameraPhotoOutput; @@ -96,6 +95,7 @@ export class PhotoManager implements OutputManager { // [End create_photo_output] // [Start set_photo_cb_double] + // Save camera photo async mediaLibSavePhoto(photoAsset: photoAccessHelper.PhotoAsset, phAccessHelper: photoAccessHelper.PhotoAccessHelper): Promise { try { @@ -104,8 +104,8 @@ export class PhotoManager implements OutputManager { assetChangeRequest.saveCameraPhoto(); await phAccessHelper.applyChanges(assetChangeRequest); phAccessHelper.release(); - } catch (err) { - Logger.error(TAG_LOG, `apply saveCameraPhoto failed with error: ${err.code}, ${err.message}`); + } catch (error) { + Logger.error(TAG_LOG, `apply saveCameraPhoto failed with error: ${error.code}, ${error.message}`); } } @@ -118,12 +118,10 @@ export class PhotoManager implements OutputManager { return; } let imageSource = image.createImageSource(data); - imageSource.createPixelMap((err: BusinessError, data: image.PixelMap) => { - if (err) { - Logger.error(TAG_LOG, `createPixelMap err:${err.code}`); - return; - } - callback(data, photoAsset.uri); + imageSource.createPixelMap().then((pixelMap: image.PixelMap) => { + callback(pixelMap, photoAsset.uri); + }).catch((err: BusinessError) => { + Logger.error(TAG_LOG, `createPixelMap err:${err.code}`); }) } } @@ -132,7 +130,11 @@ export class PhotoManager implements OutputManager { deliveryMode: photoAccessHelper.DeliveryMode.FAST_MODE, } const handler = new MediaDataHandler(); - await photoAccessHelper.MediaAssetManager.requestImageData(context, photoAsset, requestOptions, handler); + try { + await photoAccessHelper.MediaAssetManager.requestImageData(context, photoAsset, requestOptions, handler); + } catch (exception) { + Logger.error(TAG_LOG, `requestImageData failed, code is ${exception.code}, message is ${exception.message}`); + } } public setPhotoOutputCbDouble(cameraPhotoOutput: camera.PhotoOutput) { @@ -148,63 +150,29 @@ export class PhotoManager implements OutputManager { // [End set_photo_cb_double] // [Start set_photo_cb_single] + // Set photo callback single setPhotoOutputCbSingle(photoOutput: camera.PhotoOutput, context: Context) { photoOutput.on('photoAvailable', (errCode: BusinessError, photo: camera.Photo): void => { if (errCode || photo === undefined) { Logger.error(TAG_LOG, 'getPhoto failed'); return; } - this.mediaLibSavePhotoSingle(photo.main, context) - }); - } - - mediaLibSavePhotoSingle(imageObj: image.Image, context: Context) { - imageObj.getComponent(image.ComponentType.JPEG, async (errCode: BusinessError, component: image.Component) => { - Logger.info(LOG_TAG, 'getComponent start'); - if (errCode || component === undefined) { - Logger.error(LOG_TAG, 'getComponent failed'); - return; - } - let buffer: ArrayBuffer; - if (component.byteBuffer) { - buffer = component.byteBuffer; - } else { - Logger.error(LOG_TAG, 'byteBuffer is null'); - return; - } - let photoType: photoAccessHelper.PhotoType = photoAccessHelper.PhotoType.IMAGE; - let extension: string = 'jpg'; - let options: photoAccessHelper.CreateOptions = { - title: 'testPhoto' - } - let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = - photoAccessHelper.MediaAssetChangeRequest.createAssetRequest(context, photoType, extension, options); - assetChangeRequest.addResource(photoAccessHelper.ResourceType.IMAGE_RESOURCE, buffer) - assetChangeRequest.saveCameraPhoto(); - let accessHelper: photoAccessHelper.PhotoAccessHelper = - photoAccessHelper.getPhotoAccessHelper(context); - await accessHelper.applyChanges(assetChangeRequest); - let imageSource = image.createImageSource(buffer); - let pixelmap = imageSource.createPixelMapSync(); - this.callback(pixelmap, assetChangeRequest.getAsset().uri); - accessHelper.release(); - imageObj.release(); + this.mediaLibSavePhotoSingle(context, photo.main) }); } // [End set_photo_cb_single] // [Start save_photo_single] + // Save photo single mediaLibSavePhotoSingle(context: Context, imageObj: image.Image) { imageObj.getComponent(image.ComponentType.JPEG, async (errCode: BusinessError, component: image.Component) => { if (errCode || component === undefined) { Logger.error(TAG_LOG, 'getComponent failed'); return; } - let buffer: ArrayBuffer; - if (component.byteBuffer) { - buffer = component.byteBuffer; - } else { + const buffer: ArrayBuffer = component.byteBuffer; + if (!buffer) { Logger.error(TAG_LOG, 'byteBuffer is null'); return; } @@ -213,18 +181,23 @@ export class PhotoManager implements OutputManager { let options: photoAccessHelper.CreateOptions = { title: 'testPhoto' } - let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = - photoAccessHelper.MediaAssetChangeRequest.createAssetRequest(context, photoType, extension, options); - assetChangeRequest.addResource(photoAccessHelper.ResourceType.IMAGE_RESOURCE, buffer) - assetChangeRequest.saveCameraPhoto(); - let accessHelper: photoAccessHelper.PhotoAccessHelper = - photoAccessHelper.getPhotoAccessHelper(context); - await accessHelper.applyChanges(assetChangeRequest); - let imageSource = image.createImageSource(buffer); - let pixelmap = imageSource.createPixelMapSync(); - this.callback(pixelmap, assetChangeRequest.getAsset().uri); - accessHelper.release(); - imageObj.release(); + try { + let assetChangeRequest: photoAccessHelper.MediaAssetChangeRequest = + photoAccessHelper.MediaAssetChangeRequest.createAssetRequest(context, photoType, extension, options); + assetChangeRequest.addResource(photoAccessHelper.ResourceType.IMAGE_RESOURCE, buffer) + assetChangeRequest.saveCameraPhoto(); + let accessHelper: photoAccessHelper.PhotoAccessHelper = + photoAccessHelper.getPhotoAccessHelper(context); + await accessHelper.applyChanges(assetChangeRequest); + let imageSource = image.createImageSource(buffer); + let pixelmap = imageSource.createPixelMapSync(); + this.callback(pixelmap, assetChangeRequest.getAsset().uri); + accessHelper.release(); + imageObj.release(); + } catch (exception) { + Logger.error(TAG_LOG, + `mediaLibSavePhotoSingle failed, code is ${exception.code}, message is ${exception.message}`); + } }); } @@ -252,30 +225,45 @@ 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. let colorSpace: colorSpaceManager.ColorSpace = isHdr ? colorSpaceManager.ColorSpace.DISPLAY_P3 : colorSpaceManager.ColorSpace.SRGB; let colorSpaces: Array = []; try { colorSpaces = session.getSupportedColorSpaces(); } catch (error) { - let err = error as BusinessError; - Logger.error(TAG_LOG, `The getSupportedColorSpaces call failed. error code: ${err.code}`); + Logger.error(TAG_LOG, `The getSupportedColorSpaces call failed. error code: ${error.code}`); } - let isSupportedColorSpaces = colorSpaces.indexOf(colorSpace) >= 0; - if (isSupportedColorSpaces) { + if (!colorSpaces.includes(colorSpace)) { + Logger.info(TAG_LOG, `colorSpace: ${colorSpace} is not support`); + return; + } + try { + Logger.info(TAG_LOG, `setColorSpace: ${colorSpace}`); session.setColorSpace(colorSpace); + } catch (exception) { + Logger.error(TAG_LOG, `setColorSpace failed, code is ${exception.code}, message is ${exception.message}`); + } + try { let activeColorSpace: colorSpaceManager.ColorSpace = session.getActiveColorSpace(); - } else { - Logger.error(TAG_LOG, `colorSpace: ${colorSpace} is not support`); + Logger.info(TAG_LOG, `activeColorSpace: ${activeColorSpace}`); + } catch (error) { + Logger.error(TAG_LOG, `getActiveColorSpace Faild: ${error.message}`); } } + // [End set_color_space] public checkFlash(photoSession: camera.PhotoSession) { let flashModeStatus: boolean = false; - if (photoSession.hasFlash()) { - flashModeStatus = photoSession.isFlashModeSupported(camera.FlashMode.FLASH_MODE_AUTO); + try { + if (photoSession.hasFlash()) { + flashModeStatus = photoSession.isFlashModeSupported(camera.FlashMode.FLASH_MODE_AUTO); + } + } catch (exception) { + Logger.error(TAG_LOG, `checkFlash failed, code is ${exception.code}, message is ${exception.message}`); } return flashModeStatus; } @@ -286,8 +274,7 @@ export class PhotoManager implements OutputManager { photoSession.setFlashMode(flashMode || camera.FlashMode.FLASH_MODE_CLOSE); } } catch (error) { - let err = error as BusinessError; - Logger.error(TAG_LOG, `Failed to hasFlash. error: ${JSON.stringify(err)}`); + Logger.error(TAG_LOG, `Failed to hasFlash. error: ${JSON.stringify(error)}`); } } @@ -299,9 +286,8 @@ export class PhotoManager implements OutputManager { photoSession.setFocusMode(focusMode || defaultMode); } } catch (error) { - let err = error as BusinessError; Logger.error(TAG_LOG, - `Failed to check whether the focus mode is supported. error: ${JSON.stringify(err)}`); + `Failed to check whether the focus mode is supported. error: ${JSON.stringify(error)}`); } } @@ -309,8 +295,7 @@ export class PhotoManager implements OutputManager { try { photoSession.setFocusPoint(focusPoint); } catch (error) { - let err = error as BusinessError; - Logger.error(TAG_LOG, `The setFocusPoint call failed. error code: ${err.code}`); + Logger.error(TAG_LOG, `The setFocusPoint call failed. error code: ${error.code}`); } } @@ -323,11 +308,14 @@ export class PhotoManager implements OutputManager { photoZoomRatio = zoomRatioRange[0]; } } catch (error) { - let err = error as BusinessError; - Logger.error(TAG_LOG, `Failed to get the zoom ratio range. error: ${JSON.stringify(err)}`); + Logger.error(TAG_LOG, `Failed to get the zoom ratio range. error: ${JSON.stringify(error)}`); } } - photoSession.setZoomRatio(zoomRatio || photoZoomRatio); + try { + photoSession.setZoomRatio(zoomRatio || photoZoomRatio); + } catch (exception) { + Logger.error(TAG_LOG, `setZoomRatio failed, code is ${exception.code}, message is ${exception.message}`); + } } getSupportedColorSpaces(session: camera.PhotoSession): Array { @@ -335,32 +323,19 @@ export class PhotoManager implements OutputManager { try { colorSpaces = session.getSupportedColorSpaces(); } catch (error) { - let err = error as BusinessError; - Logger.error(LOG_TAG,`The getSupportedColorSpaces call failed. error code: ${err.code}`); + Logger.error(TAG_LOG, `The getSupportedColorSpaces call failed. error code: ${error.code}`); } return colorSpaces; } - setColorSpaceBeforeCommitConfig(session: camera.PhotoSession, isHdr: boolean): void { - let colorSpace: colorSpaceManager.ColorSpace = - isHdr ? colorSpaceManager.ColorSpace.DISPLAY_P3 : colorSpaceManager.ColorSpace.SRGB; - let colorSpaces: Array = this.getSupportedColorSpaces(session); - let isSupportedColorSpaces = colorSpaces.indexOf(colorSpace) >= 0; - if (isSupportedColorSpaces) { - session.setColorSpace(colorSpace); - } else { - Logger.info(LOG_TAG,`colorSpace: ${colorSpace} is not support`); - } - } - // [Start get_photo_rotation] + // Get photo rotation getPhotoRotation(photoOutput: camera.PhotoOutput, deviceDegree: number): camera.ImageRotation { let photoRotation: camera.ImageRotation = camera.ImageRotation.ROTATION_0; try { photoRotation = photoOutput.getPhotoRotation(deviceDegree); } catch (error) { - let err = error as BusinessError; - Logger.error(TAG_LOG, `The photoOutput.getPhotoRotation call failed. error code: ${err.code}`); + Logger.error(TAG_LOG, `The photoOutput.getPhotoRotation call failed. error code: ${error.code}`); } return photoRotation; } @@ -368,6 +343,7 @@ export class PhotoManager implements OutputManager { // [End get_photo_rotation] // [Start capture_photo] + // Capture photo public async capture(isFront: boolean) { const degree = await this.getPhotoDegree(); const rotation = this.getPhotoRotation(this.output!, degree); @@ -384,6 +360,7 @@ export class PhotoManager implements OutputManager { Logger.info(TAG_LOG, 'Callback invoked to indicate the photo capture request success.'); }); } + // [End capture_photo] private calculateDeviceDegree(x: number, y: number, z: number): number { @@ -392,36 +369,45 @@ export class PhotoManager implements OutputManager { if ((x * x + y * y) * 3 < z * z) { return deviceDegree; } else { - // 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) / Math.PI * 180); - // Adjust angle to be relative to vertical orientation - deviceDegree = 90 - Number(sc); - // Normalize angle to 0-360 degrees range - deviceDegree = deviceDegree >= 0 ? deviceDegree % 360 : deviceDegree % 360 + 360; + try { + // 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) / Math.PI * 180); + // Adjust angle to be relative to vertical orientation + deviceDegree = 90 - Number(sc); + // Normalize angle to 0-360 degrees range + deviceDegree = deviceDegree >= 0 ? deviceDegree % 360 : deviceDegree % 360 + 360; + } catch (exception) { + Logger.error(TAG_LOG, + `calculateDeviceDegree failed, code is ${exception.code}, message is ${exception.message}`); + } } return deviceDegree; } private getPhotoDegree() { const promise: Promise = new Promise(resolve => { - sensor.once(sensor.SensorId.ACCELEROMETER, (data: sensor.AccelerometerResponse) => { - let degree = this.calculateDeviceDegree(data.x, data.y, data.z); - resolve(degree); - }); + try { + sensor.once(sensor.SensorId.ACCELEROMETER, (data: sensor.AccelerometerResponse) => { + let degree = this.calculateDeviceDegree(data.x, data.y, data.z); + resolve(degree); + }); + } catch (exception) { + Logger.error(TAG_LOG, `getPhotoDegree failed, code is ${exception.code}, message is ${exception.message}`); + } }) return promise; } // [Start is_moving_photo] + // Check whether support moving photo or not public isMovingPhotoSupported(photoOutput: camera.PhotoOutput): boolean { let isSupported: boolean = false; try { isSupported = photoOutput.isMovingPhotoSupported(); } catch (error) { - let err = error as BusinessError; - Logger.error(TAG_LOG, `The isMovingPhotoSupported call failed. error code: ${err.code}`); + Logger.error(TAG_LOG, `The isMovingPhotoSupported call failed. error code: ${error.code}`); } return isSupported; } @@ -429,20 +415,25 @@ export class PhotoManager implements OutputManager { // [End is_moving_photo] // [Start enable_moving_photo] + // Enable moving photo public enableMovingPhoto(enabled: boolean): void { try { this.output?.enableMovingPhoto(enabled); } catch (error) { - let err = error as BusinessError; - Logger.error(TAG_LOG, `The enableMovingPhoto call failed. error code: ${err.code}`); + Logger.error(TAG_LOG, `The enableMovingPhoto call failed. error code: ${error.code}`); } } // [End enable_moving_photo] // [Start photo_release] + // Release photo async release() { - await this.output?.release(); + try { + await this.output?.release(); + } catch (exception) { + Logger.error(TAG_LOG, `release failed, code is ${exception.code}, message is ${exception.message}`); + } if (this.isSingle) { this.output?.off('photoAvailable'); } else { @@ -450,5 +441,6 @@ export class PhotoManager implements OutputManager { } this.output = undefined; } + // [End photo_release] } diff --git a/camera/src/main/ets/cameramanagers/PreviewManager.ets b/camera/src/main/ets/cameramanagers/PreviewManager.ets index 4b270250682f7008fc6d06007237f03d6130fd28..d11877b13b86730c451318102032cc94496e91ea 100644 --- a/camera/src/main/ets/cameramanagers/PreviewManager.ets +++ b/camera/src/main/ets/cameramanagers/PreviewManager.ets @@ -14,8 +14,8 @@ */ import { camera } from '@kit.CameraKit'; -import { Logger } from 'commons/src/main/ets/utils/Logger'; import { BusinessError } from '@kit.BasicServicesKit'; +import { Logger } from 'commons'; import OutputManager, { CreateOutputConfig } from './OutputManager'; import CameraConstant from '../constants/CameraConstants'; @@ -24,7 +24,8 @@ const TAG_LOG = 'PreviewManager' export class PreviewManager implements OutputManager { output?: camera.PreviewOutput; isActive: boolean = true; - onPreviewStart: () => void = () => {}; + onPreviewStart: () => void = () => { + }; constructor(onPreviewStart: () => void) { this.onPreviewStart = onPreviewStart; @@ -32,10 +33,10 @@ export class PreviewManager implements OutputManager { // [Start createOutput] async createOutput(config: CreateOutputConfig) { - const cameraOutputCap = config.cameraManager.getSupportedOutputCapability(config.device, config.sceneMode); + 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; - const previewProfile = cameraOutputCap.previewProfiles + const previewProfile = cameraOutputCap?.previewProfiles .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; @@ -46,10 +47,17 @@ export class PreviewManager implements OutputManager { Logger.error(TAG_LOG, 'Failed to get preview profile'); return; } - this.output = config.cameraManager.createPreviewOutput(previewProfile, config.surfaceId); - this.addOutputListener(this.output); + try { + this.output = config.cameraManager?.createPreviewOutput(previewProfile, config.surfaceId); + if (this.output) { + this.addOutputListener(this.output); + } + } catch (exception) { + Logger.error(TAG_LOG, `createPreviewOutput failed, code is ${exception.code}, message is ${exception.message}`); + } return this.output; } + // [End createOutput] addOutputListener(output: camera.PreviewOutput) { @@ -61,7 +69,7 @@ export class PreviewManager implements OutputManager { addFrameStartEventListener(output: camera.PreviewOutput) { output.on('frameStart', (err: BusinessError) => { if (err !== undefined && err.code !== 0) { - Logger.error(TAG_LOG, `FrameStart callback Error, errorCode: ${err.code}`); + Logger.error(TAG_LOG, `FrameStart callback Error, errorMessage: ${err.message}`); return; } Logger.info(TAG_LOG, 'Preview frame started'); @@ -72,34 +80,42 @@ export class PreviewManager implements OutputManager { addFrameEndEventListener(output: camera.PreviewOutput) { output.on('frameEnd', (err: BusinessError) => { if (err !== undefined && err.code !== 0) { - Logger.error(TAG_LOG, `FrameStart callback Error, errorCode: ${err.code}`); + Logger.error(TAG_LOG, `FrameStart callback Error, errorMessage: ${err.message}`); return; } Logger.info(TAG_LOG, 'Preview frame end'); }); } + // [End onFrame] // [Start release] async release() { - await this.output?.release(); + try { + await this.output?.release(); + } catch (exception) { + Logger.error(TAG_LOG, `release failed, code is ${exception.code}, message is ${exception.message}`); + } this.output = undefined; } + // [End release] // [Start getSupportedFrameRates] getSupportedFrameRates() { return this.output?.getSupportedFrameRates(); } + // [End getSupportedFrameRates] // [Start setFrameRate] setFrameRate(minFps: number, maxFps: number) { try { this.output?.setFrameRate(minFps, maxFps); - } catch(e) { + } catch (e) { Logger.error(TAG_LOG, 'setFrameRate error ' + JSON.stringify(e)); } } + // [End setFrameRate] } diff --git a/camera/src/main/ets/cameramanagers/VideoManager.ets b/camera/src/main/ets/cameramanagers/VideoManager.ets index 7dff1afb577ee39cf17e5761a50cfc243daac12d..cc36fb9def8790e5cbda671eaa2dc7a2e06c561f 100644 --- a/camera/src/main/ets/cameramanagers/VideoManager.ets +++ b/camera/src/main/ets/cameramanagers/VideoManager.ets @@ -20,11 +20,10 @@ import { fileIo } from '@kit.CoreFileKit'; import { sensor } from '@kit.SensorServiceKit'; import { Decimal } from '@kit.ArkTS'; import { image } from '@kit.ImageKit'; -import { BusinessError } from '@kit.BasicServicesKit'; +import { colorSpaceManager } from '@kit.ArkGraphics2D'; +import { Logger } from 'commons'; import OutputManager, { CreateOutputConfig } from './OutputManager'; import CameraConstant from '../constants/CameraConstants'; -import { colorSpaceManager } from '@kit.ArkGraphics2D'; -import { Logger } from 'commons/src/main/ets/utils/Logger'; const TAG_LOG = 'video'; @@ -75,7 +74,7 @@ export class VideoManager implements OutputManager { Logger.info(TAG_LOG, 'on avRecorder state change: ', state) }); } catch (error) { - Logger.info(TAG_LOG, 'createAVRecorder call failed. error code: %{public}s', (error as BusinessError).code); + Logger.info(TAG_LOG, 'createAVRecorder call failed. error code: %{public}s', error.code); } if (this.avRecorder === undefined || this.avRecorder === null) { return; @@ -95,14 +94,22 @@ export class VideoManager implements OutputManager { Logger.info(TAG_LOG, 'Succeeded in preparing'); } } catch (error) { - Logger.info(TAG_LOG, `Failed to prepare and catch error is ${error.message}`); + Logger.info(TAG_LOG, `Failed to prepare and catch error is ${error.message}`); } } + isSupportMirror() { + let isSupported: boolean | undefined = this.output?.isMirrorSupported(); + return isSupported; + } + // [Start start_video] - async start() { + async start(isFront: boolean) { try { if (this.avRecorder?.state === AVRecorderState.PREPARED) { + if (this.isSupportMirror() && isFront) { + this.output?.enableMirror(true) + } // [StartExclude start_video] await this.avRecorder.updateRotation(this.getVideoRotation(await this.getGravity())); // [EndExclude start_video] @@ -110,9 +117,10 @@ export class VideoManager implements OutputManager { await this.avRecorder?.start(); } } catch (error) { - Logger.info(TAG_LOG, `Failed to start and catch error is ${error.message}`); + Logger.info(TAG_LOG, `Failed to start and catch error is ${error.message}`); } } + // [End start_video] // [Start stop_video] @@ -128,9 +136,10 @@ export class VideoManager implements OutputManager { } } } catch (error) { - Logger.info(TAG_LOG, `Failed to stop and catch error is ${error.message}`); + Logger.info(TAG_LOG, `Failed to stop and catch error is ${error.message}`); } } + // [End stop_video] // [Start pause_video] @@ -141,9 +150,10 @@ export class VideoManager implements OutputManager { await this.output?.stop(); } } catch (error) { - Logger.info(TAG_LOG, `Failed to pause and catch error is ${error.message}`); + Logger.info(TAG_LOG, `Failed to pause and catch error is ${error.message}`); } } + // [End pause_video] // [Start resume_video] @@ -154,21 +164,27 @@ export class VideoManager implements OutputManager { await this.avRecorder.resume(); } } catch (error) { - Logger.info(TAG_LOG, `Failed to resume and catch error is ${error.message}`); + Logger.info(TAG_LOG, `Failed to resume and catch error is ${error.message}`); } } + // [End resume_video] // [Start release_video] async release() { - await this.avRecorder?.release(); - await this.output?.release(); - this.file && await fileIo.close(this.file.fd); + try { + await this.avRecorder?.release(); + await this.output?.release(); + this.file && await fileIo.close(this.file.fd); + } catch (exception) { + Logger.error(TAG_LOG, `release failed, code is ${exception.code}, message is ${exception.message}`); + } this.avRecorder?.off('stateChange'); this.avRecorder = undefined; this.output = undefined; this.file = undefined; } + // [End release_video] getCurrentOutput() { @@ -180,7 +196,7 @@ export class VideoManager implements OutputManager { } // [Start create_video_output] - async createVideoOutput(cameraManager: camera.CameraManager) { + async createVideoOutput(cameraManager: camera.CameraManager|undefined) { if (!this.avRecorder || this.avRecorder.state !== AVRecorderState.PREPARED) { return; } @@ -188,71 +204,79 @@ export class VideoManager implements OutputManager { // [Start get_surface_id] let videoSurfaceId = await this.avRecorder.getInputSurface(); // [End get_surface_id] - this.output = cameraManager.createVideoOutput(this.videoProfile, videoSurfaceId); + this.output = cameraManager?.createVideoOutput(this.videoProfile, videoSurfaceId); } catch (error) { Logger.error(TAG_LOG, - `Failed to create the output instance. error code: ${(error as BusinessError).code}`); + `Failed to create the output instance. error code: ${error.code}`); } } - setVideoProfile(cameraManager: camera.CameraManager, targetProfile: camera.Profile, + setVideoProfile(cameraManager: camera.CameraManager|undefined, targetProfile: camera.Profile, device: camera.CameraDevice) { this.cameraPosition = device.cameraPosition; let cameraOutputCap: camera.CameraOutputCapability | undefined = - cameraManager.getSupportedOutputCapability(device, + cameraManager?.getSupportedOutputCapability(device, camera.SceneMode.NORMAL_VIDEO); let videoProfilesArray: camera.VideoProfile[] | undefined = cameraOutputCap?.videoProfiles; - const displayRatio = targetProfile.size.height / targetProfile.size.width; - const videoProfile = videoProfilesArray?.find((profile: camera.VideoProfile) => { - const profileRatio = profile.size.height / profile.size.width - if (this.cameraPosition === 1) { - return profile.size.width >= 1080 && profile.size.height >= 1080 && - Math.abs(profileRatio - displayRatio) <= CameraConstant.PROFILE_DIFFERENCE && - profile.frameRateRange.max === 30 && profile.format === camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP; - } - if (this.qualityLevel === QualityLevel.NORMAL) { - return profile.size.width <= 1920 && profile.size.width >= 1080 && profile.size.height >= 1080 && - Math.abs(profileRatio - displayRatio) <= CameraConstant.PROFILE_DIFFERENCE * profile.size.width && - profile.frameRateRange.max === 60 && profile.format === camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP; - } - if (this.qualityLevel === QualityLevel.HIGHER && this.cameraPosition === 0) { - return profile.size.width <= 4096 && profile.size.width >= 3000 && - Math.abs(profileRatio - displayRatio) <= CameraConstant.PROFILE_DIFFERENCE * profile.size.width && - profile.frameRateRange.max === 60 && profile.format === camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP; + if (videoProfilesArray?.length) { + try { + const displayRatio = targetProfile.size.width / targetProfile.size.height; + const profileWidth = targetProfile.size.width; + const videoProfile = videoProfilesArray + .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 Math.abs(pfDisplayRatio - displayRatio) <= CameraConstant.PROFILE_DIFFERENCE && + pf.format === camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP; + }); + if (!videoProfile) { + Logger.error(TAG_LOG, 'Failed to get video profile'); + return; + } + this.videoProfile = videoProfile; + } catch (error) { + Logger.error(TAG_LOG, `Failed to createPhotoOutput. error: ${JSON.stringify(error)}`); } - return false; - }) - if (!videoProfile) { - Logger.info(TAG_LOG, 'Failed to get videoProfile') } - this.videoProfile = videoProfile; } + // [End create_video_output] + getCameraImageRotation(): camera.ImageRotation { + return this.cameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT + ? camera.ImageRotation.ROTATION_270 + : camera.ImageRotation.ROTATION_90 + } + async setAVConfig() { // [Start create_file] let options: photoAccessHelper.CreateOptions = { title: Date.now().toString() }; - let accessHelper: photoAccessHelper.PhotoAccessHelper = photoAccessHelper.getPhotoAccessHelper(this.context); - this.videoUri = await accessHelper.createAsset(photoAccessHelper.PhotoType.VIDEO, 'mp4', options); - this.file = fileIo.openSync(this.videoUri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE); + let videoAccessHelper: photoAccessHelper.PhotoAccessHelper = photoAccessHelper.getPhotoAccessHelper(this.context); + try { + this.videoUri = await videoAccessHelper.createAsset(photoAccessHelper.PhotoType.VIDEO, 'mp4', options); + this.file = fileIo.openSync(this.videoUri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE); + } catch (exception) { + Logger.error(TAG_LOG, `createAsset failed, code is ${exception.code}, message is ${exception.message}`); + } // [End create_file] // [Start av_profile] this.avProfile = { - audioBitrate: 48000, - audioChannels: 2, - audioCodec: media.CodecMimeType.AUDIO_AAC, - audioSampleRate: 48000, - fileFormat: media.ContainerFormatType.CFT_MPEG_4, - videoBitrate: 32000000, + audioBitrate: 48000, // Audio bitrate (unit: bps), which affects audio quality + audioChannels: 2, // Stereo two-channel recording + audioCodec: media.CodecMimeType.AUDIO_AAC, // The audio encoding format is AAC + audioSampleRate: 48000, // Audio sampling rate (unit: Hz), CD-quality sound + 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) ? - media.CodecMimeType.VIDEO_HEVC : media.CodecMimeType.VIDEO_AVC, - videoFrameWidth: this.videoProfile?.size.width, - videoFrameHeight: this.videoProfile?.size.height, - videoFrameRate: this.cameraPosition === 0 ? 60 : 30, - } + 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 + }; // [End av_profile] // [Start av_config] @@ -260,10 +284,9 @@ 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.cameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT ? - camera.ImageRotation.ROTATION_270.toString() : camera.ImageRotation.ROTATION_90.toString(), + videoOrientation: this.getCameraImageRotation().toString() } } // [End av_config] @@ -278,14 +301,18 @@ export class VideoManager implements OutputManager { if ((x * x + y * y) * 3 < z * z) { return getDeviceDegree; } else { - // 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) - // Adjust angle to be relative to vertical orientation - getDeviceDegree = 90 - Number(sc); - // Normalize angle to 0-360 degrees range - getDeviceDegree = getDeviceDegree >= 0 ? getDeviceDegree % 360 : getDeviceDegree % 360 + 360; + try { + // 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) + // Adjust angle to be relative to vertical orientation + getDeviceDegree = 90 - Number(sc); + // Normalize angle to 0-360 degrees range + getDeviceDegree = getDeviceDegree >= 0 ? getDeviceDegree % 360 : getDeviceDegree % 360 + 360; + } catch (exception) { + Logger.error(TAG_LOG, `getRealData failed, code is ${exception.code}, message is ${exception.message}`); + } } return getDeviceDegree; } @@ -316,26 +343,23 @@ export class VideoManager implements OutputManager { return promise; } } catch (error) { - Logger.info(TAG_LOG, `Failed to getGravity and catch error is ${error.message}`); + Logger.info(TAG_LOG, `Failed to getGravity and catch error is ${error.message}`); return 0 } } // [Start get_video_rotation] getVideoRotation(deviceDegree: number): camera.ImageRotation { - let videoRotation: camera.ImageRotation = - this.cameraPosition === camera.CameraPosition.CAMERA_POSITION_FRONT ? camera.ImageRotation.ROTATION_270 : - camera.ImageRotation.ROTATION_90; + let videoRotation: camera.ImageRotation = this.getCameraImageRotation(); try { - if (this.output) { - videoRotation = this.output.getVideoRotation(deviceDegree); - Logger.info(TAG_LOG, `Video rotation is: ${videoRotation}`); - } + 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.info(TAG_LOG, `Failed to getVideoRotation and catch error is: ${error.message}`); } return videoRotation; } + // [End get_video_rotation] async getVideoThumbnail() { @@ -355,7 +379,7 @@ export class VideoManager implements OutputManager { pixelMap = await avImageGenerator.fetchFrameByTime(timeUs, queryOption, param); avImageGenerator.release(); } catch (error) { - Logger.info(TAG_LOG, `Failed to getVideoThumbnail and catch error is ${error.message}`); + Logger.info(TAG_LOG, `Failed to getVideoThumbnail and catch error is ${error.message}`); } return pixelMap; } @@ -366,47 +390,62 @@ export class VideoManager implements OutputManager { // [Start set_video_stabilization] - setVideoStabilizationMode(session: camera.VideoSession): boolean { + setVideoStabilizationMode(session: camera.VideoSession): boolean { let mode: camera.VideoStabilizationMode = camera.VideoStabilizationMode.AUTO; // Check whether video stabilization is supported - let isSupported: boolean = session.isVideoStabilizationModeSupported(mode); - if (isSupported) { + try { + let isSupported: boolean = session.isVideoStabilizationModeSupported(mode); + if (!isSupported) { + Logger.info(TAG_LOG, `videoStabilizationMode: ${mode} is not support`); + return false; + } Logger.info(TAG_LOG, `setVideoStabilizationMode: ${mode}`); // Set video stabilization session.setVideoStabilizationMode(mode); let activeVideoStabilizationMode = session.getActiveVideoStabilizationMode(); Logger.info(TAG_LOG, `activeVideoStabilizationMode: ${activeVideoStabilizationMode}`); - } else { - Logger.info(TAG_LOG, `videoStabilizationMode: ${mode} is not support`); + return isSupported; + } catch (exception) { + Logger.error(TAG_LOG, + `setVideoStabilizationMode failed, code is ${exception.code}, message is ${exception.message}`); + return false; } - return isSupported; } + // [End set_video_stabilization] // [Start set_video_color_space] getSupportedColorSpaces(session: camera.VideoSession): Array { - let colorSpaces: Array = []; + let colorSpaces: colorSpaceManager.ColorSpace[] = []; try { colorSpaces = session.getSupportedColorSpaces(); } catch (error) { - let err = error as BusinessError; - Logger.error(TAG_LOG, `The getSupportedColorSpaces call failed. error code: ${err.code}`); + Logger.error(TAG_LOG, `The getSupportedColorSpaces call failed. error code: ${error.message}`); } return colorSpaces; } setColorSpaceAfterCommitConfig(session: camera.VideoSession, isHdr: boolean): void { - let colorSpace: colorSpaceManager.ColorSpace = isHdr? colorSpaceManager.ColorSpace.BT2020_HLG_LIMIT : colorSpaceManager.ColorSpace.BT709_LIMIT; - let colorSpaces: Array = this.getSupportedColorSpaces(session); - let isSupportedColorSpaces = colorSpaces.indexOf(colorSpace) >= 0; - if (isSupportedColorSpaces) { + let colorSpace: colorSpaceManager.ColorSpace = + isHdr ? colorSpaceManager.ColorSpace.BT2020_HLG_LIMIT : colorSpaceManager.ColorSpace.BT709_LIMIT; + let colorSpaces: colorSpaceManager.ColorSpace[] = this.getSupportedColorSpaces(session); + if (!colorSpaces.includes(colorSpace)) { + Logger.info(TAG_LOG, `colorSpace: ${colorSpace} is not support`); + return; + } + try { Logger.info(TAG_LOG, `setColorSpace: ${colorSpace}`); session.setColorSpace(colorSpace); - let activeColorSpace:colorSpaceManager.ColorSpace = session.getActiveColorSpace(); + } catch (exception) { + Logger.error(TAG_LOG, `setColorSpace failed, code is ${exception.code}, message is ${exception.message}`); + } + try { + let activeColorSpace: colorSpaceManager.ColorSpace = session.getActiveColorSpace(); Logger.info(TAG_LOG, `activeColorSpace: ${activeColorSpace}`); - } else { - Logger.info(TAG_LOG, `colorSpace: ${colorSpace} is not support`); + } catch (error) { + Logger.error(TAG_LOG, `getActiveColorSpace Faild: ${error.message}`); } } + // [Start set_video_color_space] } \ No newline at end of file diff --git a/camera/src/main/ets/components/LevelIndicator.ets b/camera/src/main/ets/components/LevelIndicator.ets index b3e6ac57d1c83d978e9842ad459dec00dfaf6791..da5302a37312d6f5162f073a41f4ccb70dd7ef48 100644 --- a/camera/src/main/ets/components/LevelIndicator.ets +++ b/camera/src/main/ets/components/LevelIndicator.ets @@ -15,8 +15,10 @@ import { curves, display } from '@kit.ArkUI'; import { sensor } from '@kit.SensorServiceKit'; +import { Logger } from 'commons'; const ANGLE_DIFFERENCE: number = 3; +const TAG = 'LevelIndicator'; // [Start LevelIndicator] @Component @@ -24,7 +26,13 @@ export struct LevelIndicator { @Prop acc: sensor.AccelerometerResponse; getRotate() { - const rotation = display.getDefaultDisplaySync().rotation * 90; + 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 rotation = (displayDefault?.rotation ?? 0) * 90; if (rotation === 90 || rotation === 270) { return -Math.atan2(-this.acc.y, this.acc.x) * (180 / Math.PI); } @@ -42,7 +50,7 @@ export struct LevelIndicator { width: 200, height: 1 }) - // [StartExclude LevelIndicator] + // [StartExclude LevelIndicator] .stroke(Color.White) .endPoint([200, 0]) // [EndExclude LevelIndicator] @@ -55,7 +63,7 @@ export struct LevelIndicator { playMode: PlayMode.Normal }) Circle() - // [StartExclude LevelIndicator] + // [StartExclude LevelIndicator] .width(48) .height(48) .stroke(Color.White) @@ -70,4 +78,5 @@ export struct LevelIndicator { .hitTestBehavior(HitTestMode.Transparent) } } + // [End LevelIndicator] diff --git a/entry/oh-package-lock.json5 b/entry/oh-package-lock.json5 deleted file mode 100644 index 01723a1209faf07a9d88680f47976b49b776a0e9..0000000000000000000000000000000000000000 --- a/entry/oh-package-lock.json5 +++ /dev/null @@ -1,29 +0,0 @@ -{ - "meta": { - "stableOrder": true, - "enableUnifiedLockfile": false - }, - "lockfileVersion": 3, - "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", - "specifiers": { - "camera@../camera": "camera@../camera", - "commons@../commons": "commons@../commons" - }, - "packages": { - "camera@../camera": { - "name": "camera", - "version": "1.0.0", - "resolved": "../camera", - "registryType": "local", - "dependencies": { - "commons": "file:../commons" - } - }, - "commons@../commons": { - "name": "commons", - "version": "1.0.0", - "resolved": "../commons", - "registryType": "local" - } - } -} \ 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 5a54fe08914b3757eab25158e6c98fff13125f69..63a319b6d0c08a541568b7e723e8fd367785b0b7 100644 --- a/entry/src/main/ets/constants/Constants.ets +++ b/entry/src/main/ets/constants/Constants.ets @@ -26,7 +26,6 @@ class CameraConstant { 'ohos.permission.READ_IMAGEVIDEO', 'ohos.permission.APPROXIMATELY_LOCATION' ]; - static readonly PROFILE_DIFFERENCE = 1e-10; } export default CameraConstant; \ No newline at end of file diff --git a/entry/src/main/ets/entryability/EntryAbility.ets b/entry/src/main/ets/entryability/EntryAbility.ets index fe3138ba2df7682844b5eba1433f0a721afde588..3c346dc45817df9f8653bded83b97e44641ddf9b 100644 --- a/entry/src/main/ets/entryability/EntryAbility.ets +++ b/entry/src/main/ets/entryability/EntryAbility.ets @@ -22,8 +22,12 @@ const DOMAIN = 0x0000; export default class EntryAbility extends UIAbility { onCreate(): void { - this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); - hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + try { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } catch (exception) { + hilog.error(DOMAIN, 'testTag', `setColorMode failed, code is ${exception.code}, message is ${exception.message}`); + } } onDestroy(): void { @@ -34,7 +38,12 @@ export default class EntryAbility extends UIAbility { // Main window is created, set main page for this ability hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); - WindowUtil.enterImmersive(windowStage.getMainWindowSync()); + try { + WindowUtil.enterImmersive(windowStage.getMainWindowSync()); + } catch (exception) { + hilog.error(DOMAIN, 'testTag', + `getMainWindowSync failed, code is ${exception.code}, message is ${exception.message}`); + } windowStage.loadContent('pages/Index', (err) => { if (err.code) { diff --git a/entry/src/main/ets/pages/Index.ets b/entry/src/main/ets/pages/Index.ets index 0d236628a89673d6d577a18788614e47eb19e141..77a65ed8bd296dd828318fcf15d8ddfb7f89bbca 100644 --- a/entry/src/main/ets/pages/Index.ets +++ b/entry/src/main/ets/pages/Index.ets @@ -13,24 +13,38 @@ * limitations under the License. */ +import { sensor } from '@kit.SensorServiceKit'; +import { common } from '@kit.AbilityKit'; +import { display, window } from '@kit.ArkUI'; +import { curves } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { camera } from '@kit.CameraKit'; +import { + CameraManager, + GridLine, + ImageReceiverManager, + LevelIndicator, + MetadataManager, + PhotoManager, + PreviewManager, + VideoManager +} from 'camera'; import CameraConstant from '../constants/Constants'; -import { calCameraPoint, getClampedChildPosition, limitNumberInRange, showToast } from '../utils/CommonUtil'; +import { + calCameraPoint, + calFaceBoxLinePoint, + getClampedChildPosition, + limitNumberInRange, + showToast +} from '../utils/CommonUtil'; import RefreshableTimer from '../utils/RefreshableTimer'; import PermissionManager from '../utils/PermissionManager'; import ZoomButtonsView from '../views/ZoomButtonsView'; import ModeButtonsView from '../views/ModeButtonsView'; import SettingButtonsView from '../views/SettingButtonsView'; import OperateButtonsView from '../views/OperateButtonsView'; -import { sensor } from '@kit.SensorServiceKit'; -import { common } from '@kit.AbilityKit'; -import { display } from '@kit.ArkUI'; -import PreviewViewModel from '../viewmodels/PreviewViewModel'; -import { CameraManager, - GridLine, - ImageReceiverManager, - LevelIndicator, - PhotoManager, PreviewManager, VideoManager } from 'camera'; -import { curves } from '@kit.ArkUI'; +import PreviewViewModel, { LinePoint } from '../viewmodels/PreviewViewModel'; +import { Logger } from 'commons'; @Extend(SymbolGlyph) function funcButtonStyle() { @@ -41,66 +55,80 @@ function funcButtonStyle() { .backgroundColor('#664D4D4D') } +const TAG = 'Index'; + @Entry @Component struct Index { private context: Context = this.getUIContext().getHostContext()!; private applicationContext = this.context.getApplicationContext(); - private windowClass = (this.context as common.UIAbilityContext).windowStage.getMainWindowSync(); - + private windowClass: window.Window | undefined = undefined; @State videoManager: VideoManager = new VideoManager(this.context); @State isSinglePhoto: boolean = false; @State isLivePhoto: boolean = false; private photoManager: PhotoManager = new PhotoManager(this.context, true, this.isSinglePhoto); - private previewManager: PreviewManager = new PreviewManager(() => { this.onPreviewStart() }); + private previewManager: PreviewManager = new PreviewManager(() => { + this.onPreviewStart() + }); private imageReceiverManager: ImageReceiverManager = new ImageReceiverManager(px => { this.onImageReceiver(px); }); + private metadataManager: MetadataManager = new MetadataManager(faceBoxArr => { + this.onMetadataObjectsAvailable(faceBoxArr); + }); private cameraManager: CameraManager = new CameraManager(this.context, [this.previewManager, - this.photoManager, this.videoManager, this.imageReceiverManager]); - + this.photoManager, this.videoManager, this.imageReceiverManager, this.metadataManager]); @State previewVM: PreviewViewModel = new PreviewViewModel(this.getUIContext()); - @State isGridLineVisible: boolean = false; @State isLevelIndicatorVisible: boolean = false; @State isPreviewImageVisible: boolean = false; - @State isFocusBoxVisible: boolean = false; @State focusBoxPosition: Edges = { top: 0, left: 0 }; private focusBoxSize: Size = { width: 80, height: 80 }; private focusBoxTimer: RefreshableTimer = new RefreshableTimer(() => { this.isFocusBoxVisible = false; }, 3 * 1000); - + private exposureFontSize: number = 24; @State isSleeping: boolean = false; private sleepTimer?: RefreshableTimer; - private zoomRange: number[] = []; @State zooms: number[] = [1, 5, 10]; @State currentZoom: number = 1; @State isZoomPinching: boolean = false; private originZoomBeforePinch: number = 1; // record zoom after pinch, sale base it. - + @State isStabilizationEnabled: boolean = false; @State previewImage: PixelMap | ResourceStr = ''; private PreviewImageHeight: number = 80; - @State photoDelayTime: number = 0; @State photoRemainder: number = 0; - @State isDelayTakePhoto:boolean = false; - + @State isDelayTakePhoto: boolean = false; @State acc: sensor.AccelerometerResponse = { x: 0, y: 0, z: 0 } as sensor.AccelerometerResponse; - private setPreviewSize: () => void = () => { this.previewVM.setPreviewSize(); } - + private setPreviewSize: () => void = () => { + this.previewVM.setPreviewSize(); + } @State isShowBlack: boolean = false; @StorageLink('captureClick') @Watch('onCaptureClick') captureClickFlag: number = 0; @State flashBlackOpacity: number = 1; + @State faceBoundingBoxArr: camera.Rect[] = []; async aboutToAppear() { this.addGravityEventListener(); this.initSleepTimer(); this.registerApplicationStateChange(); this.addOrientationChangeEventListener(); - display.on('foldStatusChange', () => { this.onFoldStatusChange() }); + try { + this.windowClass = (this.context as common.UIAbilityContext).windowStage.getMainWindowSync(); + } catch (exception) { + Logger.error(TAG, `getMainWindowSync failed, code is ${exception.code}, message is ${exception.message}`); + } + + try { + display.on('foldStatusChange', () => { + this.onFoldStatusChange() + }); + } catch (exception) { + Logger.error(TAG, `onFoldStatusChange failed, code is ${exception.code}, message is ${exception.message}`); + } } aboutToDisappear(): void { @@ -109,18 +137,23 @@ struct Index { // [Start addGravityEventListener] addGravityEventListener() { - sensor.on(sensor.SensorId.GRAVITY, (data) => { - this.acc = data; - }, { interval: 100 * 1000 * 1000 }); // 100ms + try { + sensor.on(sensor.SensorId.GRAVITY, (data) => { + this.acc = data; + }, { interval: 100 * 1000 * 1000 }); // 100ms + } catch (exception) { + Logger.error(TAG, `addGravityEventListener failed, code is ${exception.code}, message is ${exception.message}`); + } } + // [End addGravityEventListener] addOrientationChangeEventListener() { - this.windowClass.on('windowSizeChange', this.setPreviewSize); + this.windowClass?.on('windowSizeChange', this.setPreviewSize); } removeOrientationChangeEventListener() { - this.windowClass.off('windowSizeChange', this.setPreviewSize); + this.windowClass?.off('windowSizeChange', this.setPreviewSize); } onImageReceiver(pixelMap: PixelMap) { @@ -140,6 +173,7 @@ struct Index { this.sleepTimer?.refresh(); }); } + // [End initSleepTimer] async onFoldStatusChange() { @@ -171,16 +205,32 @@ struct Index { const sceneMode = this.previewVM.getSceneMode(); await this.cameraManager.start(this.previewVM.surfaceId, cameraPosition, sceneMode, this.previewVM.getProfile); } + // [End registerApplicationStateChange] exitApp() { - this.applicationContext.killAllProcesses(); + this.applicationContext.killAllProcesses().catch((err: BusinessError) => { + Logger.error('showToast', `showToast failed, code is ${err.code}, message is ${err.message}`); + }); } onPreviewStart() { this.previewVM.closePreviewBlur(); } + // [Start onMetadataObjectsAvailable] + onMetadataObjectsAvailable(faceBoxArr: camera.Rect[]) { + 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.faceBoundingBoxArr = faceBoxArr; + } + // [End onMetadataObjectsAvailable] + initZooms() { const zoomRange = this.cameraManager.getZoomRange(); const minZoom = zoomRange[0]; @@ -198,7 +248,8 @@ struct Index { this.previewVM.rates = [minRate, maxRate]; this.previewVM.currentRate = maxRate; this.previewManager.setFrameRate(maxRate, maxRate); - }; + } + ; } syncButtonSettings() { @@ -226,6 +277,19 @@ struct Index { this.flashBlackAnim(); } + // [Start faceBox] + @Builder + faceBox(faceBoxRect: camera.Rect) { + ForEach(calFaceBoxLinePoint(faceBoxRect), (linePoint: LinePoint) => { + Line() + .startPoint([0, 0]) + .endPoint([linePoint.increment.x, linePoint.increment.y]) + .stroke(Color.White) + .position({ x: linePoint.start.x, y: linePoint.start.y }) + }) + } + // [End faceBox] + @Builder preview() { // [Start Stack] @@ -238,12 +302,14 @@ struct Index { type: XComponentType.SURFACE, controller: this.previewVM.xComponentController }) - // [StartExclude Stack] - // [StartExclude XComponent_gesture] + // [StartExclude Stack] + // [StartExclude XComponent_gesture] .onLoad(async () => { // [StartExclude XComponent] await PermissionManager.request(CameraConstant.PERMISSIONS, this.context) - .catch(() => { this.exitApp() }); + .catch(() => { + this.exitApp() + }); // [EndExclude XComponent] this.previewVM.surfaceId = this.previewVM.xComponentController.getXComponentSurfaceId(); this.previewVM.setPreviewSize(); @@ -261,8 +327,12 @@ struct Index { .onActionStart(() => { this.originZoomBeforePinch = this.currentZoom; this.isZoomPinching = true; + this.sleepTimer?.refresh(); }) .onActionUpdate((event: GestureEvent) => { + if (this.previewVM.isVideoMode() && this.isStabilizationEnabled) { + return; + } const targetZoom = this.originZoomBeforePinch * event.scale; this.currentZoom = limitNumberInRange(targetZoom, this.zoomRange); this.cameraManager.setZoomRatio(this.currentZoom); @@ -282,10 +352,11 @@ struct Index { previewSize.height ); this.cameraManager.setFocusPoint(cameraPoint); + this.cameraManager.setMeteringPoint(cameraPoint); this.focusBoxPosition = getClampedChildPosition(this.focusBoxSize, { width: this.getUIContext().px2vp(previewSize.width), height: this.getUIContext().px2vp(previewSize.height) - }, event) + }, event); this.focusBoxTimer.refresh(); }) // [EndExclude Stack] @@ -304,9 +375,19 @@ struct Index { .width(80) .height(80) .position(this.focusBoxPosition) + SymbolGlyph($r('sys.symbol.sun_max')) + .fontSize(this.exposureFontSize) + .fontColor([Color.White]) + .position(this.getExposurePosition()) } - if(this.isDelayTakePhoto){ + if (!this.isFocusBoxVisible) { + ForEach(this.faceBoundingBoxArr, (value: camera.Rect) => { + this.faceBox(value); + }) + } + + if (this.isDelayTakePhoto) { Text(`${this.photoRemainder}S`) .fontSize(44) .fontWeight(FontWeight.Regular) @@ -316,7 +397,7 @@ struct Index { if (this.isShowBlack) { Column() - .key('black') + .id('black') .width('100%') .height('100%') .backgroundColor(Color.Black) @@ -355,6 +436,7 @@ struct Index { this.syncButtonSettings(); }) } + // [End wakeupMask] @Builder @@ -385,24 +467,45 @@ struct Index { @Builder previewImageButton() { - SymbolGlyph( - this.isPreviewImageVisible ? $r('sys.symbol.eye') : $r('sys.symbol.eye_slash') - ) + SymbolGlyph(this.isPreviewImageVisible ? $r('sys.symbol.eye') : $r('sys.symbol.eye_slash')) .funcButtonStyle() .onClick(() => { this.isPreviewImageVisible = !this.isPreviewImageVisible; - const message = this.isPreviewImageVisible ? $r('app.string.preview_image_open') : $r('app.string.preview_image_close'); + const message = + this.isPreviewImageVisible ? $r('app.string.preview_image_open') : $r('app.string.preview_image_close'); showToast(this.getUIContext(), message); }) } getPreviewImageWidth() { - const rotation = display.getDefaultDisplaySync().rotation * 90; + 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 rotation = (displayDefault?.rotation ?? 0) * 90; const ratio = this.previewVM.getPreviewRatio(); const displayRatio = rotation === 90 || rotation === 270 ? 1 / ratio : ratio; return this.PreviewImageHeight / displayRatio; } + getExposurePosition(): Edges { + const focusBoxLeft = this.focusBoxPosition.left as number; + const focusBoxTop = this.focusBoxPosition.top as number; + const exposureWidth = this.exposureFontSize; + const exposureHeight = this.exposureFontSize; + const focusBoxWidth = this.focusBoxSize.width; + const focusBoxHeight = this.focusBoxSize.height; + const previewWidth = this.getUIContext().px2vp(this.previewVM.previewSize.width); + const GAP = 10; + const top = focusBoxTop - exposureHeight / 2 + focusBoxHeight / 2; + const left = focusBoxLeft > previewWidth / 2 + ? focusBoxLeft - GAP - exposureWidth + : focusBoxLeft + focusBoxWidth + GAP; + return { top, left }; + } + @Builder previewImageView() { Image(this.previewImage) @@ -450,10 +553,12 @@ struct Index { videoManager: this.videoManager, photoDelayTime: this.photoDelayTime, isSinglePhoto: this.isSinglePhoto, - isLivePhoto: this.isLivePhoto + isLivePhoto: this.isLivePhoto, + isStabilizationEnabled: this.isStabilizationEnabled }) if (!this.photoRemainder) { - if (!this.previewVM.isFront) { + if (!this.previewVM.isFront && + !(this.isStabilizationEnabled && this.previewVM.isVideoMode())) { ZoomButtonsView({ cameraManager: this.cameraManager, zoomRange: this.zoomRange, @@ -490,6 +595,7 @@ struct Index { .height('100%') .width('100%') .backgroundColor(Color.Black) - .onClick(() => {}) + .onClick(() => { + }) } } \ No newline at end of file diff --git a/entry/src/main/ets/utils/CommonUtil.ets b/entry/src/main/ets/utils/CommonUtil.ets index fcf47ba52d1008455efad159b4c1ef32803399b5..e14870ab18c789c771a7784b1a8f00a78c1158d6 100644 --- a/entry/src/main/ets/utils/CommonUtil.ets +++ b/entry/src/main/ets/utils/CommonUtil.ets @@ -16,11 +16,22 @@ import { Point } from '@kit.TestKit'; import { camera } from '@kit.CameraKit'; import { display } from '@kit.ArkUI'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { Logger } from 'commons'; +import { LinePoint } from '../viewmodels/PreviewViewModel'; + +const FACE_BOX_LINE_RATIO: number = 0.3; export function limitNumberInRange(src: number, range: number[]) { - if (range.length < 2) return src; - if (src < range[0]) return range[0]; - if (src > range[1]) return range[1]; + if (range.length < 2) { + return src; + } + if (src < range[0]) { + return range[0]; + } + if (src > range[1]) { + return range[1]; + } return src; } @@ -41,7 +52,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); } @@ -71,6 +82,38 @@ export function getClampedChildPosition(childSize: Size, parentSize: Size, point } // [End getClampedChildPosition] +// [Start calFaceBoxLinePoint] +// Calculate the coordinates of the face detection box. +export function calFaceBoxLinePoint(faceBoxRect: camera.Rect): LinePoint[] { + // The length of the sides of the box. + let lineLength: number = Math.min(faceBoxRect.width, faceBoxRect.height) * FACE_BOX_LINE_RATIO; + let linePoints: LinePoint[] = []; + + // The coordinates of the four vertices of the detection box. + let startPoints: camera.Point[] = [ + { x: faceBoxRect.topLeftX, y: faceBoxRect.topLeftY }, + { x: faceBoxRect.topLeftX + faceBoxRect.width, y: faceBoxRect.topLeftY }, + { x: faceBoxRect.topLeftX, y: faceBoxRect.topLeftY + faceBoxRect.height }, + { x: faceBoxRect.topLeftX + faceBoxRect.width, y: faceBoxRect.topLeftY + faceBoxRect.height }]; + + // Calculate the relative coordinates of each edge. + startPoints.forEach((startPoint: camera.Point) => { + let HorizontalLine: LinePoint = { + start: startPoint, + increment: { x: startPoint.x > faceBoxRect.topLeftX ? -lineLength : lineLength, y: 0 } + }; + + let verticalLine: LinePoint = { + start: startPoint, + increment: { x: 0, y: startPoint.y > faceBoxRect.topLeftY ? -lineLength : lineLength } + }; + + linePoints.push(HorizontalLine, verticalLine); + }); + return linePoints; +} +// [End calFaceBoxLinePoint] + export function showToast( UIContext: UIContext, message: ResourceStr = '', @@ -83,12 +126,20 @@ export function showToast( duration, alignment, offset + }).catch((err: BusinessError) => { + Logger.error('showToast', `showToast failed, code is ${err.code}, message is ${err.message}`); }); } // [Start calCameraPoint] export function calCameraPoint(eventX: number, eventY: number, width: number, height: number): camera.Point { - const displayRotation = display.getDefaultDisplaySync().rotation * 90; + let displayDefault: display.Display | null = null; + try { + displayDefault = display.getDefaultDisplaySync(); + } catch (exception) { + Logger.error('calCameraPoint', `calCameraPoint failed, code is ${exception.code}, message is ${exception.message}`); + } + const displayRotation = (displayDefault?.rotation ?? 0) * 90; if (displayRotation === 0) { return { x: eventY / height, y: 1 - eventX / width }; } @@ -100,4 +151,5 @@ export function calCameraPoint(eventX: number, eventY: number, width: number, he } return { x: eventX / width, y: eventY / height }; } + // [End calCameraPoint] \ No newline at end of file diff --git a/entry/src/main/ets/utils/PermissionManager.ets b/entry/src/main/ets/utils/PermissionManager.ets index af0c98fd9278d25d60e26f37b875c4edf2ca3a88..95de42ca8d4f1063e501950295a02931f005205a 100644 --- a/entry/src/main/ets/utils/PermissionManager.ets +++ b/entry/src/main/ets/utils/PermissionManager.ets @@ -14,7 +14,7 @@ */ import { abilityAccessCtrl, Context, Permissions } from '@kit.AbilityKit'; -import { Logger } from 'commons/src/main/ets/utils/Logger'; +import { Logger } from 'commons'; const TAG = 'PermissionManager'; @@ -23,18 +23,23 @@ class PermissionManager { private static atManager: abilityAccessCtrl.AtManager = abilityAccessCtrl.createAtManager(); static async request(permissions: Permissions[], context: Context) { - const data = await PermissionManager.atManager.requestPermissionsFromUser(context, permissions); - const grantStatus: number[] = data.authResults; - const deniedPermissions = permissions.filter((_, i) => grantStatus[i] !== 0); - for (const permission of deniedPermissions) { - const secondGrantStatus = await PermissionManager.atManager.requestPermissionOnSetting(context, [permission]); - if (secondGrantStatus[0] !== 0) { - Logger.error(TAG, 'permission denied'); - throw new Error('permission denied'); + try { + const data = await PermissionManager.atManager.requestPermissionsFromUser(context, permissions); + const grantStatus: number[] = data.authResults; + const deniedPermissions = permissions.filter((_, i) => grantStatus[i] !== 0); + for (const permission of deniedPermissions) { + const secondGrantStatus = await PermissionManager.atManager.requestPermissionOnSetting(context, [permission]); + if (secondGrantStatus[0] !== 0) { + Logger.error(TAG, 'permission denied'); + throw new Error('permission denied'); + } } + } catch (exception) { + Logger.error(TAG, `request failed, code is ${exception.code}, message is ${exception.message}`); } } } + // [End request_permissions] export default PermissionManager; \ No newline at end of file diff --git a/entry/src/main/ets/utils/WindowUtil.ets b/entry/src/main/ets/utils/WindowUtil.ets index bad30c83ff61c3837b08fad9b0e9c36949e2f73e..0d9bc91690df90af78208f3c5b42f4799b155a8c 100644 --- a/entry/src/main/ets/utils/WindowUtil.ets +++ b/entry/src/main/ets/utils/WindowUtil.ets @@ -14,7 +14,7 @@ */ import { display, window } from '@kit.ArkUI'; -import { Logger } from 'commons/src/main/ets/utils/Logger'; +import { Logger } from 'commons'; const TAG = 'WindowUtil' @@ -30,10 +30,16 @@ class WindowUtil { // Given a ratio, obtain the maximum display width and height based on the screen width and height. static getMaxDisplaySize(ratio: number): Size { - const defaultDisplay: display.Display = display.getDefaultDisplaySync(); - const windowWidth: number = defaultDisplay.width; - const windowHeight: number = defaultDisplay.height; - const rotation = display.getDefaultDisplaySync().rotation * 90; + let defaultDisplay: display.Display | null = null; + try { + defaultDisplay = display.getDefaultDisplaySync(); + } 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; if (rotation === 90 || rotation === 270) { ratio = 1 / ratio; } @@ -53,8 +59,13 @@ class WindowUtil { } static getWindowRatio(): number { - const defaultDisplay: display.Display = display.getDefaultDisplaySync(); - return defaultDisplay.height / defaultDisplay.width; + try { + const defaultDisplay: display.Display = display.getDefaultDisplaySync(); + return defaultDisplay.height / defaultDisplay.width; + } catch (exception) { + Logger.error(TAG, `getWindowRatio failed, code is ${exception.code}, message is ${exception.message}`); + return 0; + } } } diff --git a/entry/src/main/ets/viewmodels/PreviewViewModel.ets b/entry/src/main/ets/viewmodels/PreviewViewModel.ets index 60fa3e0a9e08e7f3cf7b1de386597f37614d0eb2..c5a4b7e1c3ee194202428dd561fb0b70438a92c8 100644 --- a/entry/src/main/ets/viewmodels/PreviewViewModel.ets +++ b/entry/src/main/ets/viewmodels/PreviewViewModel.ets @@ -1,13 +1,36 @@ -import CameraConstant from '../constants/Constants'; +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 ("the License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { curves, display } from '@kit.ArkUI'; import { camera } from '@kit.CameraKit'; import WindowUtil from '../utils/WindowUtil'; -import { curves, display } from '@kit.ArkUI'; +import CameraConstant from '../constants/Constants'; +import { Logger } from 'commons'; export enum CameraMode { PHOTO, VIDEO } +export interface LinePoint { + start: camera.Point, + increment: camera.Point +} + +const TAG = 'PreviewViewModel'; + /** * States and methods related to preview. */ @@ -23,7 +46,7 @@ class PreviewViewModel { rates?: number[] = []; currentRate: number = 0; blurRadius: number = 0; - blurRotation: RotateOptions = { y: 0.5, angle: 0}; + blurRotation: RotateOptions = { y: 0.5, angle: 0 }; constructor(uiContext: UIContext) { this.uiContext = uiContext; @@ -35,6 +58,7 @@ class PreviewViewModel { ? camera.CameraPosition.CAMERA_POSITION_FRONT : camera.CameraPosition.CAMERA_POSITION_BACK; } + // [End isFront] getPreviewRatio() { @@ -52,7 +76,13 @@ class PreviewViewModel { // [Start getProfile] getProfile: (cameraOrientation: number) => camera.Profile = cameraOrientation => { const displaySize: Size = WindowUtil.getMaxDisplaySize(this.getPreviewRatio()); - const displayRotation = display.getDefaultDisplaySync().rotation * 90; + 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) * 90; const isRevert = (cameraOrientation + displayRotation) % 180 !== 0; return { format: camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP, @@ -62,6 +92,7 @@ class PreviewViewModel { } }; } + // [End getProfile] // [Start setPreviewSize] @@ -73,6 +104,7 @@ class PreviewViewModel { surfaceHeight: displaySize.height }); } + // [End setPreviewSize] getPreviewTop() { diff --git a/entry/src/main/ets/views/OperateButtonsView.ets b/entry/src/main/ets/views/OperateButtonsView.ets index 067d1514f13e1785b9cdb7ba7e4fcbb30427a0c2..7de83e3bfa7f117ea8554d6cfd0b0e3338d40f5c 100644 --- a/entry/src/main/ets/views/OperateButtonsView.ets +++ b/entry/src/main/ets/views/OperateButtonsView.ets @@ -15,9 +15,12 @@ import { image } from '@kit.ImageKit'; import { common } from '@kit.AbilityKit'; -import { camera } from '@kit.CameraKit'; -import PreviewViewModel from '../viewmodels/PreviewViewModel'; +import { BusinessError } from '@kit.BasicServicesKit'; import { AVRecorderState, CameraManager, PhotoManager, VideoManager } from 'camera'; +import PreviewViewModel from '../viewmodels/PreviewViewModel'; +import { Logger } from 'commons'; + +const TAG = 'OperateButtonsView'; @Component struct OperateButtonsView { @@ -28,7 +31,7 @@ struct OperateButtonsView { @Require photoManager: PhotoManager; @Prop @Require photoDelayTime: number; @Link photoRemainder: number; - private photoDelayTimer:number = 0; + private photoDelayTimer: number = 0; @State thumbnail: image.PixelMap | string = ''; @State thumbnailUrl: string = ''; @Require syncButtonSettings: () => void; @@ -68,15 +71,15 @@ struct OperateButtonsView { if (this.photoDelayTime) { this.isDelayTakePhoto = true; this.photoRemainder = this.photoDelayTime; - this.photoDelayTimer = setInterval(()=>{ + this.photoDelayTimer = setInterval(() => { this.photoRemainder--; - if(this.photoRemainder === 0){ + if (this.photoRemainder === 0) { this.photoManager.capture(this.previewVM.isFront); this.captureClickFlag++; this.isDelayTakePhoto = false; clearTimeout(this.photoDelayTimer); } - },1000) + }, 1000) } else { this.photoManager.capture(this.previewVM.isFront); this.captureClickFlag++; @@ -106,7 +109,7 @@ struct OperateButtonsView { }) .justifyContent(FlexAlign.Center) .onClick(() => { - this.videoManager.start(); + this.videoManager.start(this.previewVM.isFront); }) } @@ -179,13 +182,15 @@ struct OperateButtonsView { .borderWidth(this.thumbnail ? 1 : 0) .borderColor(Color.White) .clip(true) - .onClick(()=>{ - if(this.thumbnailUrl){ + .onClick(() => { + if (this.thumbnailUrl) { this.context.startAbility({ parameters: { uri: this.thumbnailUrl }, action: 'ohos.want.action.viewData', bundleName: 'com.huawei.hmos.photos', abilityName: 'com.huawei.hmos.photos.MainAbility' + }).catch((err: BusinessError) => { + Logger.error(TAG, `showToast failed, code is ${err.code}, message is ${err.message}`); }); } }) @@ -230,6 +235,7 @@ struct OperateButtonsView { // [EndExclude toggleCameraPositionButton] }) } + // [End toggleCameraPositionButton] build() { diff --git a/entry/src/main/ets/views/SettingButtonsView.ets b/entry/src/main/ets/views/SettingButtonsView.ets index c44004d2daf998a9d849154845ad94df48565da7..559f433380af10bdf8a2bcf6588118f2cd839efd 100644 --- a/entry/src/main/ets/views/SettingButtonsView.ets +++ b/entry/src/main/ets/views/SettingButtonsView.ets @@ -70,7 +70,7 @@ struct SettingButtonsView { @Require photoManager: PhotoManager; @Link videoManager: VideoManager; @Link photoDelayTime: number; - @State isStabilizationEnabled: boolean = false; + @Link isStabilizationEnabled: boolean; @Link isSinglePhoto: boolean; @Link previewVM: PreviewViewModel; diff --git a/entry/src/main/ets/views/ZoomButtonsView.ets b/entry/src/main/ets/views/ZoomButtonsView.ets index ccdef4750b5fa7025cb7e6a40a42a97b6cb54012..1c943b7e522fa118aaa33b763e5d9d43d16908da 100644 --- a/entry/src/main/ets/views/ZoomButtonsView.ets +++ b/entry/src/main/ets/views/ZoomButtonsView.ets @@ -54,8 +54,8 @@ struct ZoomButtonsView { .borderRadius('50%') .textAlign(TextAlign.Center) .onClick(() => { - this.cameraManager.setSmoothZoom(zoom) - this.currentZoom = zoom + this.cameraManager.setSmoothZoom(zoom); + this.currentZoom = zoom; }) }, (zoom: number) => zoom.toString()) } diff --git a/entry/src/main/module.json5 b/entry/src/main/module.json5 index db6c962a82533649d0733527e55ed68cc8ba55fa..01eaa8b29b5deabd3dfb66e6a99010ffce78bfc5 100644 --- a/entry/src/main/module.json5 +++ b/entry/src/main/module.json5 @@ -83,19 +83,19 @@ ] }, }, - // [EndExclude permissions_acc] { - "name": "ohos.permission.ACCELEROMETER", - "reason": "$string:permission_SENSOR", + "name": "ohos.permission.APPROXIMATELY_LOCATION", + "reason": "$string:permission_LOCATION", "usedScene": { "abilities": [ "EntryAbility" ] } }, + // [EndExclude permissions_acc] { - "name": "ohos.permission.APPROXIMATELY_LOCATION", - "reason": "$string:permission_LOCATION", + "name": "ohos.permission.ACCELEROMETER", + "reason": "$string:permission_SENSOR", "usedScene": { "abilities": [ "EntryAbility" diff --git a/entry/src/main/resources/base/element/string.json b/entry/src/main/resources/base/element/string.json index e4effd6419d8fb8089dae253c15fbd363c77ac21..32d0de3f681b25d4009cffd841f63483b0e3a0d8 100644 --- a/entry/src/main/resources/base/element/string.json +++ b/entry/src/main/resources/base/element/string.json @@ -134,7 +134,7 @@ }, { "name": "permission_LOCATION", - "value": "用于地理位置" + "value": "For location" } ] } \ No newline at end of file diff --git a/entry/src/main/resources/en_US/element/string.json b/entry/src/main/resources/en_US/element/string.json index 06cf5aeb40bd35672cf10bd4c9c17d6f852b6744..84573dc11ed7df2080447c4a2dcd36c44ccccd45 100644 --- a/entry/src/main/resources/en_US/element/string.json +++ b/entry/src/main/resources/en_US/element/string.json @@ -131,6 +131,10 @@ { "name": "preview_image_close", "value": "Dual-channel preview has been disabled" + }, + { + "name": "permission_LOCATION", + "value": "For location" } ] } \ No newline at end of file diff --git a/entry/src/main/resources/zh_CN/element/string.json b/entry/src/main/resources/zh_CN/element/string.json index bcf0e2cff2c605952f27c43b5f8a8e261c465edf..5ec0a505fa7c75944cef88292a370e07d50dab8c 100644 --- a/entry/src/main/resources/zh_CN/element/string.json +++ b/entry/src/main/resources/zh_CN/element/string.json @@ -131,6 +131,10 @@ { "name": "preview_image_close", "value": "双路预览已关闭" + }, + { + "name": "permission_LOCATION", + "value": "用于地理位置" } ] } \ No newline at end of file diff --git a/oh-package-lock.json5 b/oh-package-lock.json5 deleted file mode 100644 index 2857b556e1b879745efca26121d68bb5d43c38cf..0000000000000000000000000000000000000000 --- a/oh-package-lock.json5 +++ /dev/null @@ -1,10 +0,0 @@ -{ - "meta": { - "stableOrder": true, - "enableUnifiedLockfile": false - }, - "lockfileVersion": 3, - "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.", - "specifiers": {}, - "packages": {} -} \ No newline at end of file diff --git a/screenshots/devices/camera.png b/screenshots/devices/camera.png new file mode 100644 index 0000000000000000000000000000000000000000..a1aae63b08f301c7dcaa364233e294d6cf7e16cd Binary files /dev/null and b/screenshots/devices/camera.png differ diff --git a/screenshots/devices/camera_en.png b/screenshots/devices/camera_en.png new file mode 100644 index 0000000000000000000000000000000000000000..fcc533c6d23b9ce1d481558c0d2b17d7d4cdab80 Binary files /dev/null and b/screenshots/devices/camera_en.png differ diff --git a/screenshots/devices/photo.png b/screenshots/devices/photo.png deleted file mode 100644 index 32c49239a739ebac845c2d4f13e7f72261e698ff..0000000000000000000000000000000000000000 Binary files a/screenshots/devices/photo.png and /dev/null differ diff --git a/screenshots/devices/video.png b/screenshots/devices/video.png deleted file mode 100644 index 40e1cf67b305ca84b0487709cfb9974f5c0f55d5..0000000000000000000000000000000000000000 Binary files a/screenshots/devices/video.png and /dev/null differ