diff --git a/OAT.xml b/OAT.xml
index c18b31e0f12e182059756552ea2d9d0b42600f88..65350456fb39c3f9f78edab14eb33d2ec457a950 100644
--- a/OAT.xml
+++ b/OAT.xml
@@ -58,7 +58,8 @@ Note:If the text contains special characters, please escape them according to th
-
+
+
diff --git a/test/hapAppDcameraSample/.gitignore b/test/hapAppDcameraSample/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b
--- /dev/null
+++ b/test/hapAppDcameraSample/.gitignore
@@ -0,0 +1,12 @@
+/node_modules
+/oh_modules
+/local.properties
+/.idea
+**/build
+/.hvigor
+.cxx
+/.clangd
+/.clang-format
+/.clang-tidy
+**/.test
+/.appanalyzer
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/AppScope/app.json5 b/test/hapAppDcameraSample/AppScope/app.json5
new file mode 100644
index 0000000000000000000000000000000000000000..52abc1bf1edb36eaeaad22bea70ff3bc58fe769e
--- /dev/null
+++ b/test/hapAppDcameraSample/AppScope/app.json5
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+{
+ "app": {
+ "bundleName": "com.example.rm0512demo",
+ "vendor": "example",
+ "versionCode": 1000000,
+ "versionName": "1.0.0",
+ "icon": "$media:app_icon",
+ "label": "$string:app_name",
+ }
+}
diff --git a/test/hapAppDcameraSample/AppScope/resources/base/element/string.json b/test/hapAppDcameraSample/AppScope/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..213d3080ee2c35ba44c313e2463a29c6005a7261
--- /dev/null
+++ b/test/hapAppDcameraSample/AppScope/resources/base/element/string.json
@@ -0,0 +1,8 @@
+{
+ "string": [
+ {
+ "name": "app_name",
+ "value": "RM0512Demo"
+ }
+ ]
+}
diff --git a/test/hapAppDcameraSample/AppScope/resources/base/media/app_icon.png b/test/hapAppDcameraSample/AppScope/resources/base/media/app_icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..a39445dc87828b76fed6d2ec470dd455c45319e3
Binary files /dev/null and b/test/hapAppDcameraSample/AppScope/resources/base/media/app_icon.png differ
diff --git a/test/hapAppDcameraSample/build-profile.json5 b/test/hapAppDcameraSample/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..ebd23c5cf83d7c422a7ff44eb28284619f96d8c0
--- /dev/null
+++ b/test/hapAppDcameraSample/build-profile.json5
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+{
+ "app": {
+ "signingConfigs": [
+ {
+ "name": "default",
+ "type": "HarmonyOS",
+ "material": {
+ "certpath": "C:\\Users\\admin\\.ohos\\config\\default_dcameraSample_MQGVIkitSxUdBu-kMBECw1W0Rx8UVies_9x-1tKD7hc=.cer",
+ "storePassword": "0000001A0DFB8F6A5166B2EFAE02A902D3A40D38BDB0CC1C505061D261ABA85CBA447876815B88989AD1",
+ "keyAlias": "debugKey",
+ "keyPassword": "0000001A0B50FDD9F5979F6DACD8E77D49CC5CDC0C6C83825EFACC3C4301E0284BB8FFA8E65362FA9DD6",
+ "profile": "C:\\Users\\admin\\.ohos\\config\\default_dcameraSample_MQGVIkitSxUdBu-kMBECw1W0Rx8UVies_9x-1tKD7hc=.p7b",
+ "signAlg": "SHA256withECDSA",
+ "storeFile": "C:\\Users\\admin\\.ohos\\config\\default_dcameraSample_MQGVIkitSxUdBu-kMBECw1W0Rx8UVies_9x-1tKD7hc=.p12"
+ }
+ }
+ ],
+ "products": [
+ {
+ "name": "default",
+ "signingConfig": "default",
+ "compatibleSdkVersion": "5.0.1(13)",
+ "runtimeOS": "HarmonyOS",
+ "buildOption": {
+ "strictMode": {
+ "caseSensitiveCheck": true,
+ "useNormalizedOHMUrl": true
+ }
+ }
+ }
+ ],
+ "buildModeSet": [
+ {
+ "name": "debug",
+ },
+ {
+ "name": "release"
+ }
+ ]
+ },
+ "modules": [
+ {
+ "name": "entry",
+ "srcPath": "./entry",
+ "targets": [
+ {
+ "name": "default",
+ "applyToProducts": [
+ "default"
+ ]
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/code-linter.json5 b/test/hapAppDcameraSample/code-linter.json5
new file mode 100644
index 0000000000000000000000000000000000000000..44d50304643a42f437232b908c9b44c51cea8f60
--- /dev/null
+++ b/test/hapAppDcameraSample/code-linter.json5
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+{
+ "files": [
+ "**/*.ets"
+ ],
+ "ignore": [
+ "**/src/ohosTest/**/*",
+ "**/src/test/**/*",
+ "**/src/mock/**/*",
+ "**/node_modules/**/*",
+ "**/oh_modules/**/*",
+ "**/build/**/*",
+ "**/.preview/**/*"
+ ],
+ "ruleSet": [
+ "plugin:@performance/recommended",
+ "plugin:@typescript-eslint/recommended"
+ ],
+ "rules": {
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/.gitignore b/test/hapAppDcameraSample/entry/.gitignore
new file mode 100644
index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/.gitignore
@@ -0,0 +1,6 @@
+/node_modules
+/oh_modules
+/.preview
+/build
+/.cxx
+/.test
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/build-profile.json5 b/test/hapAppDcameraSample/entry/build-profile.json5
new file mode 100644
index 0000000000000000000000000000000000000000..384f7363aed3352dbb05f27e16b93245dc32f882
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/build-profile.json5
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+{
+ "apiType": "stageMode",
+ "buildOption": {
+ "externalNativeOptions": {
+ "path": "./src/main/cpp2/CMakeLists.txt",
+ "arguments": "",
+ "cppFlags": ""
+ }
+ },
+ "buildOptionSet": [
+ {
+ "name": "release",
+ "arkOptions": {
+ "obfuscation": {
+ "ruleOptions": {
+ "enable": false,
+ "files": [
+ "./obfuscation-rules.txt"
+ ]
+ }
+ }
+ },
+ "nativeLib": {
+ "debugSymbol": {
+ "strip": true,
+ "exclude": []
+ }
+ }
+ },
+ ],
+ "targets": [
+ {
+ "name": "default"
+ },
+ {
+ "name": "ohosTest",
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/hvigorfile.ts b/test/hapAppDcameraSample/entry/hvigorfile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..8774588471ede4c1563f09d9a1d22f764bb1fd9e
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/hvigorfile.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 { hapTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: hapTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
+ plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
+}
diff --git a/test/hapAppDcameraSample/entry/obfuscation-rules.txt b/test/hapAppDcameraSample/entry/obfuscation-rules.txt
new file mode 100644
index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/obfuscation-rules.txt
@@ -0,0 +1,23 @@
+# Define project specific obfuscation rules here.
+# You can include the obfuscation configuration files in the current module's build-profile.json5.
+#
+# For more details, see
+# https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/source-obfuscation-V5
+
+# Obfuscation options:
+# -disable-obfuscation: disable all obfuscations
+# -enable-property-obfuscation: obfuscate the property names
+# -enable-toplevel-obfuscation: obfuscate the names in the global scope
+# -compact: remove unnecessary blank spaces and all line feeds
+# -remove-log: remove all console.* statements
+# -print-namecache: print the name cache that contains the mapping from the old names to new names
+# -apply-namecache: reuse the given cache file
+
+# Keep options:
+# -keep-property-name: specifies property names that you want to keep
+# -keep-global-name: specifies names that you want to keep in the global scope
+
+-enable-property-obfuscation
+-enable-toplevel-obfuscation
+-enable-filename-obfuscation
+-enable-export-obfuscation
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/oh-package-lock.json5 b/test/hapAppDcameraSample/entry/oh-package-lock.json5
new file mode 100644
index 0000000000000000000000000000000000000000..82a2d60c7a390c81bf7935c900ea933a44dd22a5
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/oh-package-lock.json5
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+{
+ "meta": {
+ "stableOrder": true
+ },
+ "lockfileVersion": 3,
+ "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
+ "specifiers": {},
+ "packages": {}
+}
diff --git a/test/hapAppDcameraSample/entry/oh-package.json5 b/test/hapAppDcameraSample/entry/oh-package.json5
new file mode 100644
index 0000000000000000000000000000000000000000..ca5dba866ade0eea1b1617cd5caacc7b8b0e8a8b
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/oh-package.json5
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+{
+ "name": "entry",
+ "version": "1.0.0",
+ "description": "Please describe the basic information.",
+ "main": "",
+ "author": "",
+ "license": "",
+ "dependencies": {
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/common/ButtonComponent.ets b/test/hapAppDcameraSample/entry/src/main/ets/common/ButtonComponent.ets
new file mode 100644
index 0000000000000000000000000000000000000000..9a6761392b89cba235e13b3214692cdd1e22ccf2
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/common/ButtonComponent.ets
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2022 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 { obtainImgVertical, obtainImgV, obtainImgVs } from '../utils/ImageList'
+
+interface ImageList {
+ image: Resource
+ value: string
+}
+
+@Component
+export struct ButtonComponent {
+ private onInputValue: (result: string) => void = () => {
+ }
+
+ build() {
+ Row() {
+ Grid() {
+ ForEach(obtainImgV(), (item: ImageList, index: number | undefined) => {
+ GridItem() {
+ Button({ type: ButtonType.Normal }) {
+ Image(item.image)
+ .width('45%')
+ .height('35%')
+ .objectFit(ImageFit.Cover)
+ }
+ .id(item.value)
+ .width('100%')
+ .height('100%')
+ .borderRadius('16px')
+ .backgroundColor(index !== undefined && index < 2 ? '#33007DFF' : '#FF006CDE')
+ .onClick(() => {
+ this.onInputValue(item.value)
+ })
+ }
+ .rowStart(index !== undefined ? index : 0)
+ .rowEnd(index !== undefined && index === 2 ? (index !== undefined ? index + 1 : 0) : (index !== undefined ? index : 0))
+ .columnStart(4)
+ .columnEnd(4)
+ })
+ ForEach(obtainImgVs(), (item: ImageList, index) => {
+ GridItem() {
+ Button({ type: ButtonType.Normal }) {
+ Image(item.image)
+ .width('45%')
+ .height('35%')
+ .objectFit(ImageFit.Cover)
+ }
+ .id(item.value)
+ .width('100%')
+ .height('100%')
+ .borderRadius('16px')
+ .backgroundColor('#33007DFF')
+ .onClick(() => {
+ this.onInputValue(item.value)
+ })
+ }
+ .rowStart(index !== undefined ? index : 0)
+ .rowEnd(index !== undefined ? index : 0)
+ .columnStart(3)
+ .columnEnd(3)
+ })
+ ForEach(obtainImgVertical(), (item: ImageList, index) => {
+ GridItem() {
+ Button({ type: ButtonType.Normal }) {
+ Image(item.image)
+ .width('55%')
+ .height('55%')
+ .objectFit(ImageFit.Cover)
+ }
+ .id(item.value)
+ .width('100%')
+ .height('100%')
+ .borderRadius('16px')
+ .backgroundColor('#F0F0F0')
+ .onClick(() => {
+ this.onInputValue(item.value)
+ })
+ }
+ .columnStart(index === undefined ? 0 : index % 3)
+ .columnEnd(index !== undefined && item.value === '0' ? (index % 3 + 1) : index !== undefined ? index % 3 : 0)
+ })
+ }
+ .columnsGap(5)
+ .rowsGap(5)
+ .columnsTemplate('1fr 1fr 1fr 1fr 1fr')
+ .rowsTemplate('1fr 1fr 1fr 1fr ')
+ }
+ .padding(10)
+ .size({ width: '100%', height: '56%' })
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/common/ButtonComponentHorizontal.ets b/test/hapAppDcameraSample/entry/src/main/ets/common/ButtonComponentHorizontal.ets
new file mode 100644
index 0000000000000000000000000000000000000000..37f28a99ac2709511b231f2e2ad7c04909ff8494
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/common/ButtonComponentHorizontal.ets
@@ -0,0 +1,82 @@
+/*
+ * Copyright (c) 2022 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 { obtainImgLevel, obtainImg, obtainImgL } from '../utils/ImageList'
+
+interface ImageList {
+ image: Resource
+ value: string
+}
+
+@Component
+export struct ButtonComponentHorizontal {
+ private onInputValue: (result: string) => void = () => {
+ }
+
+ build() {
+ Column() {
+ Grid() {
+ ForEach(obtainImg(), (item: ImageList, index) => {
+ GridItem() {
+ Button({ type: ButtonType.Normal }) {
+ Image(item.image)
+ .height('100%')
+ .aspectRatio(index !== undefined && index < 2 ? 1 : 0.5)
+ .objectFit(ImageFit.Contain)
+ }
+ .id(item.value)
+ .width('100%')
+ .height('100%')
+ .borderRadius(20)
+ .backgroundColor(index !== undefined && index < 2 ? '#33007DFF' : '#007DFF')
+ .onClick(() => {
+ this.onInputValue(item.value)
+ })
+ }
+ .rowStart(index !== undefined ? index : 0)
+ .rowEnd(index !== undefined && index === 2 ? (index !== undefined ? index + 1 : 0) : (index !== undefined ? index : 0))
+ .columnStart(4)
+ .columnEnd(4)
+ })
+ ForEach(obtainImgLevel(), (item:ImageList, index) => {
+ GridItem() {
+ Button({ type: ButtonType.Normal }) {
+ Image(item.image)
+ .height('100%')
+ .aspectRatio(item.value === '0' ? 2.5 : 1)
+ .objectFit(ImageFit.Contain)
+ }
+ .id(item.value)
+ .width('100%')
+ .height('100%')
+ .borderRadius(20)
+ .backgroundColor((index !== undefined && index % 4 >= 3) || item.value === '+' ? '#33007DFF' : '#F0F0F0')
+ .onClick(() => {
+ this.onInputValue(item.value)
+ })
+ }
+ .columnStart(index !== undefined ? index % 4 : 0)
+ .columnEnd(item.value === '0' ? (index !== undefined ? index % 4 + 1 : 0) : index !== undefined ? index % 4 : 0)
+ })
+ }
+ .width('80%')
+ .columnsGap(10)
+ .rowsGap(10)
+ .columnsTemplate('1fr 1fr 1fr 1fr 1fr')
+ .rowsTemplate('1fr 1fr 1fr 1fr')
+ }
+ .padding(10)
+ .size({ width: '70%', height: '90%' })
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/common/DeviceDialog.ets b/test/hapAppDcameraSample/entry/src/main/ets/common/DeviceDialog.ets
new file mode 100644
index 0000000000000000000000000000000000000000..41d2a087f84f8345f617c3a845d4b5e4f0586ce6
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/common/DeviceDialog.ets
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2022 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 deviceManager from '@ohos.distributedDeviceManager';
+import Logger from '../utils/Logger'
+
+const TAG: string = 'Sample_DeviceDialog'
+
+@CustomDialog
+export struct DeviceDialog {
+ controller?: CustomDialogController;
+ @StorageLink('deviceList') deviceList: Array = AppStorage.get('deviceList')!;
+ private selectedIndex: number | undefined = 0;
+ private onSelectedIndexChange: (selectedIndex: number | undefined) => void = () => {
+ }
+ @State deviceDialogWidth: number = 0
+
+ build() {
+ Column() {
+ Text($r('app.string.choiceDevice'))
+ .fontSize(px2vp(30))
+ .width('100%')
+ .height('20%')
+ .fontColor(Color.Black)
+ .textAlign(TextAlign.Start)
+ List() {
+ ForEach(this.deviceList, (item: deviceManager.DeviceBasicInfo, index: number | undefined) => {
+ ListItem() {
+ Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
+ Text(item.deviceName)
+ .fontSize(px2vp(30))
+ .width('100%')
+ .fontColor(Color.Black)
+ }
+ .margin({ top: 17 })
+ .enabled(true)
+ .backgroundColor(Color.Gray)
+ .onClick(() => {
+ Logger.info(TAG, `select device: ${item.deviceId}`)
+ if (this.selectedIndex !== undefined) {
+ Logger.info(TAG, `onSelectedIndexChange: ${index}`)
+ console.log(TAG, this.onSelectedIndexChange);
+ this.selectedIndex = index
+ this.onSelectedIndexChange(this.selectedIndex)
+ }
+ })
+ }
+ .width('100%')
+ .height('50%')
+ }, (item: deviceManager.DeviceBasicInfo) => item.deviceName)
+ }
+ .height('60%')
+ .width('100%')
+ .layoutWeight(1)
+
+ Button() {
+ Text($r('app.string.cancel'))
+ .width('90%')
+ .fontSize(21)
+ .fontColor('#ff0d64fb')
+ .textAlign(TextAlign.Center)
+ }
+ .type(ButtonType.Capsule)
+ .backgroundColor(Color.White)
+ .onClick(() => {
+ if (this.controller !== undefined) {
+ this.controller.close()
+ }
+ })
+ }
+ .margin({ bottom: 15 })
+ .onAreaChange((oldArea: Area, newArea: Area) => {
+ this.deviceDialogWidth =
+ (newArea.width > newArea.height ? newArea.height : newArea.width) as number * 0.1 //percentage
+ })
+ .width('80%')
+ .height(px2vp(240))
+ .padding({ left: 18, right: 32 })
+ .backgroundColor(Color.White)
+ .border({ color: Color.White, radius: 20 })
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/common/InputComponent.ets b/test/hapAppDcameraSample/entry/src/main/ets/common/InputComponent.ets
new file mode 100644
index 0000000000000000000000000000000000000000..4a588dc900b6046c1dd0dd5f1c052b7612400c03
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/common/InputComponent.ets
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2022 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.
+ */
+@Component
+export struct InputComponent {
+ @Link result: string
+ @Link expression: string
+ private isLand: boolean = false
+
+ build() {
+ Stack({ alignContent: this.isLand ? Alignment.BottomStart : Alignment.TopEnd }) {
+ Column() {
+ Row() {
+ Text(this.expression)
+ .margin({ top: '3%' })
+ .height('100%')
+ .width('100%')
+ .id('expression')
+ .opacity(0.9)
+ .fontWeight(400)
+ .textAlign(TextAlign.Start)
+ .fontSize(this.isLand ? '50px' : '35px')
+ }
+ .height('48%')
+ .width('90%')
+ .align(this.isLand ? Alignment.End : Alignment.Start)
+ Row() {
+ Text(this.result)
+ .margin({ top: '1%' })
+ .height('100%')
+ .width('100%')
+ .id('result')
+ .opacity(0.38)
+ .textAlign(TextAlign.Start)
+ .fontSize(this.isLand ? '50px' : '31px')
+ .margin(this.isLand ? { bottom: 64 } : {})
+ }
+ .height('46%')
+ .width('90%')
+ .align(this.isLand ? Alignment.End : Alignment.Start)
+ }
+ .width('100%')
+ .align(Alignment.Center)
+ }
+ .width(this.isLand ? '34%' : '100%')
+ .height(this.isLand ? '100%' : '34%')
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/common/TitleBarComponent.ets b/test/hapAppDcameraSample/entry/src/main/ets/common/TitleBarComponent.ets
new file mode 100644
index 0000000000000000000000000000000000000000..ad9f2e64ecbe41e9bebb87853c2b8e4fdcefd800
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/common/TitleBarComponent.ets
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2022 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 deviceManager from '@ohos.distributedDeviceManager';
+import Logger from '../utils/Logger'
+import { DeviceDialog } from '../common/DeviceDialog'
+import { RemoteDeviceModel, BUNDLE_NAME } from '../recorder/RemoteDeviceModel'
+import common from '@ohos.app.ability.common'
+import Want from '@ohos.app.ability.Want';
+import { router } from '@kit.ArkUI';
+
+const TAG: string = 'Sample_TitleBarComponent'
+const DATA_CHANGE: string = 'dataChange'
+const EXIT: string = 'exit'
+const DEVICE_DISCOVERY_RANGE: number = 1000
+
+@Component
+export struct TitleBarComponent {
+ @Prop isLand: boolean | null = null
+ @State selectedIndex: number | undefined = 0
+ @StorageLink('deviceList') deviceList: Array = []
+ private isShow: boolean = false
+ private startAbilityCallBack: (key: string) => void = () => {
+ }
+ private dialogController: CustomDialogController | null = null
+ private remoteDeviceModel: RemoteDeviceModel = new RemoteDeviceModel()
+ onSelectedIndexChange = (index: number | undefined) => {
+ Logger.info(TAG, `selectedIndexChange`)
+ this.selectedIndex = index
+ this.dialogController?.close()
+ /*if (this.selectedIndex === 0) {
+ Logger.info(TAG, `stop ability`)
+ this.deviceList = []
+ if (this.dialogController !== null) {
+ this.dialogController.close()
+ }
+ return
+ }*/
+ this.selectDevice()
+ }
+
+ aboutToAppear() {
+ AppStorage.setOrCreate('deviceList', this.deviceList)
+ }
+
+ clearSelectState() {
+ this.deviceList = []
+ if (this.dialogController !== null) {
+ this.dialogController.close()
+ }
+ Logger.info(TAG, `cancelDialog`)
+ if (this.remoteDeviceModel === undefined) {
+ return
+ }
+ this.remoteDeviceModel.unregisterDeviceListCallback()
+ }
+
+ selectDevice() {
+ if (this.selectedIndex !== undefined && (this.remoteDeviceModel === null || this.remoteDeviceModel.discoverList.length <= 0)) {
+ Logger.info(TAG, `continue unauthed device: ${JSON.stringify(this.deviceList)}`)
+ this.clearSelectState()
+ return
+ }
+ Logger.info(TAG, `needAuth:`)
+ if (this.selectedIndex !== undefined) {
+ console.log(TAG, ",", this.deviceList.length, "," , this.deviceList[this.selectedIndex].deviceName);
+
+ this.remoteDeviceModel.authenticateDevice(this.deviceList[this.selectedIndex], () => {
+ Logger.info(TAG, `auth and online finished`);
+ if (this.remoteDeviceModel !== null && this.remoteDeviceModel.deviceList !== null && this.selectedIndex !== undefined) {
+ for (let i = 0; i < this.remoteDeviceModel.deviceList!.length; i++) {
+ if (this.remoteDeviceModel.deviceList![i].deviceName === this.deviceList[this.selectedIndex].deviceName) {
+ }
+ }
+ }
+ })
+ }
+ this.clearSelectState()
+ }
+
+ showDiainfo() {
+ this.deviceList = []
+ // 注册监听回调,发现设备或查找到已认证设备会弹窗显示
+ this.remoteDeviceModel.registerDeviceListCallback(() => {
+ this.deviceList = []
+ let context: common.UIAbilityContext | undefined = AppStorage.get('UIAbilityContext')
+ if (context !== undefined) {
+ this.deviceList.push({
+ deviceId: '0',
+ deviceName: context.resourceManager.getStringSync($r('app.string.localhost').id),
+ deviceType: '0',
+ networkId: ''
+ })
+ }
+ let deviceTempList = this.remoteDeviceModel.discoverList.length > 0 ? this.remoteDeviceModel.discoverList : this.remoteDeviceModel.deviceList;
+ if (deviceTempList !== null) {
+ for (let i = 0; i < deviceTempList!.length; i++) {
+ Logger.info(TAG, `found device ${i}/${deviceTempList!.length} deviceId= ${deviceTempList![i].deviceId}, deviceName= ${deviceTempList![i].deviceName}, deviceType= ${deviceTempList![i].deviceType}`);
+ if (deviceTempList !== null) {
+ this.deviceList.push({
+ deviceId: deviceTempList![i].deviceId,
+ deviceName: deviceTempList![i].deviceName,
+ deviceType: deviceTempList![i].deviceType,
+ networkId: deviceTempList![i].networkId,
+ })
+ AppStorage.set('deviceList', this.deviceList)
+ }
+ }
+ }
+ })
+ if (this.dialogController === null) {
+ this.dialogController = new CustomDialogController({
+ builder: DeviceDialog({
+ selectedIndex: this.selectedIndex,
+ onSelectedIndexChange: this.onSelectedIndexChange
+ }),
+ cancel: () => {
+ this.clearSelectState()
+ },
+ autoCancel: true,
+ alignment: this.isLand ? DialogAlignment.Center : DialogAlignment.Bottom,
+ customStyle: false
+ })
+ }
+ if (this.dialogController !== null) {
+ this.dialogController.open()
+ }
+ }
+
+ build() {
+ Row() {
+ Image($r('app.media.ic_back'))
+ .height('60%')
+ .margin({ left: '5%' })
+ .width('50px')
+ .objectFit(ImageFit.Contain)
+ .onClick(async () => {
+ //let context = getContext(this) as common.UIAbilityContext
+ //context.terminateSelf()
+ router.back();
+ })
+ Text("back")
+ .height('60%')
+ .fontSize('28px')
+ .margin({ left: 12 })
+ Blank().layoutWeight(1)
+ if (!this.isShow) {
+ Image($r("app.media.ic_hop_normal1"))
+ .id('selectDevice')
+ .margin({ right: 32 })
+ .width('9%')
+ .margin({ right: '12%' })
+ .objectFit(ImageFit.Contain)
+ .onClick(() => {
+ this.showDiainfo()
+ //router.back();
+ })
+ }
+ }
+ .width('100%')
+ .height(this.isLand ? '10%' : '6%')
+ .constraintSize({ minHeight: 50 })
+ .alignItems(VerticalAlign.Center)
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/entryability/EntryAbility.ts b/test/hapAppDcameraSample/entry/src/main/ets/entryability/EntryAbility.ts
new file mode 100644
index 0000000000000000000000000000000000000000..0f5e1027543fba54fdc0e0a4aff8b16b2e30c9d5
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/entryability/EntryAbility.ts
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2023-2024 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 { UIAbility } from '@kit.AbilityKit';
+import { window } from '@kit.ArkUI';
+import { abilityAccessCtrl } from '@kit.AbilityKit';
+import { Permissions } from '@kit.AbilityKit';
+import Logger from '../utils/Logger';
+import { BusinessError } from '@ohos.base';
+
+/**
+ * Lift cycle management of Ability.
+ */
+export default class EntryAbility extends UIAbility {
+ onCreate(want, launchParam) {
+ Logger.info('Sample_VideoRecorder', 'Ability onCreate,requestPermissionsFromUser');
+ let permissionNames: Array = ['ohos.permission.MEDIA_LOCATION', 'ohos.permission.READ_MEDIA',
+ 'ohos.permission.WRITE_MEDIA', 'ohos.permission.CAMERA', 'ohos.permission.MICROPHONE',
+ 'ohos.permission.DISTRIBUTED_DATASYNC'];
+ abilityAccessCtrl.createAtManager().requestPermissionsFromUser(this.context, permissionNames).then((data) => {
+ console.log("Sample_VideoRecorder", data);
+ }).catch((err: BusinessError) => {
+ console.log("Sample_VideoRecorder", err.message);
+ });
+ }
+
+ onDestroy() {
+ Logger.info('Sample_VideoRecorder', 'Ability onDestroy');
+ }
+
+ async onWindowStageCreate(windowStage: window.WindowStage) {
+ // Main window is created, set main page for this ability
+ Logger.info('Sample_VideoRecorder', 'Ability onWindowStageCreate');
+
+ windowStage.loadContent('pages/ListPage', (err, data) => {
+ if (err.code) {
+ Logger.error('Sample_VideoRecorder', 'Failed to load the content. Cause: ' + JSON.stringify(err));
+ return;
+ }
+ Logger.info('Sample_VideoRecorder', 'Succeeded in loading the content. Data: ' + JSON.stringify(data));
+ windowStage.getMainWindow().then((win: window.Window) => {
+ win.setKeepScreenOn(true);
+ })
+ });
+ }
+
+ onWindowStageDestroy() {
+ // Main window is destroyed, release UI related resources
+ Logger.info('Sample_VideoRecorder', 'Ability onWindowStageDestroy');
+ }
+
+ onForeground() {
+ // Ability has brought to foreground
+ Logger.info('Sample_VideoRecorder', 'Ability onForeground');
+ }
+
+ onBackground() {
+ // Ability has back to background
+ Logger.info('Sample_VideoRecorder', 'Ability onBackground');
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/test/hapAppDcameraSample/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets
new file mode 100644
index 0000000000000000000000000000000000000000..69a47ce6bf1d1e2d0c5a0f0432a8bb83ef1daae4
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets
@@ -0,0 +1,26 @@
+/*
+ * 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 { hilog } from '@kit.PerformanceAnalysisKit';
+import { BackupExtensionAbility, BundleVersion } from '@kit.CoreFileKit';
+
+export default class EntryBackupAbility extends BackupExtensionAbility {
+ async onBackup() {
+ hilog.info(0x0000, 'testTag', 'onBackup ok');
+ }
+
+ async onRestore(bundleVersion: BundleVersion) {
+ hilog.info(0x0000, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion));
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/pages/Index.ets b/test/hapAppDcameraSample/entry/src/main/ets/pages/Index.ets
new file mode 100644
index 0000000000000000000000000000000000000000..ff1bd620c43aa111685245ef2b8758f95313d1ba
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/pages/Index.ets
@@ -0,0 +1,37 @@
+/*
+ * 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 { hilog } from '@kit.PerformanceAnalysisKit';
+// import testNapi from 'libentry.so';
+
+@Entry
+@Component
+struct Index {
+ @State message: string = 'Hello World';
+
+ build() {
+ Row() {
+ Column() {
+ Text(this.message)
+ .fontSize(50)
+ .fontWeight(FontWeight.Bold)
+ .onClick(() => {
+ // hilog.info(0x0000, 'testTag', 'Test NAPI 2 + 3 = %{public}d', testNapi.add(2, 3));
+ })
+ }
+ .width('100%')
+ }
+ .height('100%')
+ }
+}
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/pages/ListPage.ets b/test/hapAppDcameraSample/entry/src/main/ets/pages/ListPage.ets
new file mode 100644
index 0000000000000000000000000000000000000000..fad80ae403d7a7e671ade586e2ef3c45bd531459
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/pages/ListPage.ets
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023-2024 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 { router } from '@kit.ArkUI';
+import Logger from '../utils/Logger';
+
+@Entry
+@Component
+struct ListPage {
+ build() {
+ Column() {
+ Text($r('app.string.page_title'))
+ .fontFamily('HarmonyHeiTi-Bold')
+ .fontSize(24)
+ .fontColor($r('app.color.title_color'))
+ .height(33)
+ .width(312)
+ .margin({left: 24, right:24, top:12, bottom: 12})
+ Button() {
+ Text($r('app.string.page_title_video'))
+ .fontFamily('HarmonyHeiTi-Medium')
+ .width(64)
+ .height(20)
+ .fontColor($r('app.color.title_color'))
+ .fontSize(16)
+ .margin({left: 12, right:336, top:12, bottom: 12})
+ }
+ .id('videoRecorderPage')
+ .height(48)
+ .backgroundColor($r('app.color.button_background'))
+ .borderRadius(24)
+ .onClick(() => {
+ router.pushUrl({
+ url: 'recorder/VideoRecorder',
+ })
+ })
+ .margin({ left: 12, right:12, top: 0, bottom:12})
+ Button() {
+ Text($r('app.string.page_title_audio'))
+ .fontFamily('HarmonyHeiTi-Medium')
+ .width(64)
+ .height(20)
+ .fontColor($r('app.color.title_color'))
+ .fontSize(16)
+ .margin({left: 12, right:336, top:12, bottom: 12})
+ }
+ .id('audioRecorderPage')
+ .height(48)
+ .backgroundColor($r('app.color.button_background'))
+ .borderRadius(24)
+ .onClick(() => {
+ router.pushUrl({
+ url: 'recorder/AudioRecorder',
+ })
+ })
+ .margin({ left: 12, right:12, top: 0, bottom:12})
+ }
+ .backgroundColor($r('app.color.homepage_background'))
+ .width('100%')
+ .height('100%')
+ .alignItems(HorizontalAlign.Start)
+ }
+}
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/recorder/AudioRecorder.ets b/test/hapAppDcameraSample/entry/src/main/ets/recorder/AudioRecorder.ets
new file mode 100644
index 0000000000000000000000000000000000000000..88e8c88ecf26be82d534074f1681981e85cff2b8
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/recorder/AudioRecorder.ets
@@ -0,0 +1,488 @@
+/*
+ * Copyright (C) 2023-2024 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 { media } from '@kit.MediaKit';
+import { BusinessError } from '@kit.BasicServicesKit';
+import { dateTime } from '../utils/DateTimeUtils';
+import Logger from '../utils/Logger';
+import SaveCameraAsset from '../utils/SaveCameraAsset';
+
+const TAG: string = 'Sample_AudioRecorder';
+
+@Entry
+@Component
+struct AudioRecording {
+ private audioRecorder?: media.AVRecorder;
+ private fdPath?: string = '';
+ @State frameRate: number = 15;
+ @State audioRecorderTimeText: string = '00:00';
+ private seconds: number = 0;
+ private timer: number = 0;
+ @State displayLog: string = '';
+ private recorderState: string = 'free';
+ @State isStartRecording: boolean = false;
+ @State isPauseRecording: boolean = false;
+ private mSaveCameraAsset: SaveCameraAsset = new SaveCameraAsset(TAG);
+ private mFileAssetId?: number = 0; // init fd 0
+ @State audioChannels: number = 2; // set default audioChannels 2
+ @State audioSampleRate: number = 48000; // set default audioSampleRate 48000
+
+ private avProfile: media.AVRecorderProfile = {
+ audioBitrate: 64000, // set audioBitrate according to device ability
+ audioChannels: 1, // set audioChannels, valid value 1-8
+ audioCodec: media.CodecMimeType.AUDIO_AAC, // set audioCodec, AUDIO_AAC is the only choice
+ audioSampleRate: 8000, // set audioSampleRate according to device ability
+ fileFormat: media.ContainerFormatType.CFT_MPEG_4A, // set fileFormat, for video is m4a
+ }
+ private avConfig: media.AVRecorderConfig = {
+ audioSourceType: media.AudioSourceType.AUDIO_SOURCE_TYPE_MIC,
+ profile: this.avProfile,
+ url: 'fd://'
+ }
+
+ aboutToAppear(): void {
+ Logger.info(TAG, 'aboutToAppear called');
+ Logger.info(TAG, 'aboutToAppear done');
+ }
+
+ aboutToDisappear(): void {
+ Logger.info(TAG, 'aboutToDisappear called');
+ Logger.info(TAG, 'aboutToDisappear done');
+ }
+
+ async failureCallback(error: BusinessError): Promise {
+ Logger.info(TAG, 'case failureCallback called,errMessage is ' + JSON.stringify(error));
+ }
+
+ async catchCallback(error: BusinessError): Promise {
+ Logger.info(TAG, 'case catchCallback called,errMessage is ' + JSON.stringify(error));
+ }
+
+ // show recording time
+ getRecordTime(): void {
+ this.timer = setInterval(() => {
+ this.seconds += 1;
+ this.audioRecorderTimeText = dateTime(this.seconds);
+ }, 1000)
+ }
+
+ // create file fd
+ async getFileFd(): Promise {
+ Logger.info(TAG, 'getFileFd called');
+ this.mFileAssetId = await this.mSaveCameraAsset.createAudioFd();
+ this.fdPath = 'fd://' + this.mFileAssetId.toString();
+ this.avConfig.url = this.fdPath;
+ Logger.info(TAG, 'fdPath is: ' + this.fdPath);
+ Logger.info(TAG, 'getFileFd done');
+ }
+
+ async createAudioRecorder(): Promise {
+ await media.createAVRecorder().then((recorder) => {
+ Logger.info(TAG, 'case createAVRecorder called');
+ if (typeof (recorder) !== 'undefined') {
+ this.audioRecorder = recorder;
+ } else {
+ Logger.info(TAG, 'case create avRecorder failed!!!');
+ return
+ }
+ }, this.failureCallback).catch(this.catchCallback);
+ this.setCallback();
+ }
+
+ // set callback on
+ setCallback(): void {
+ Logger.info(TAG, 'case callback');
+ if (this.audioRecorder) {
+ this.audioRecorder.on('stateChange', (state, reason) => {
+ Logger.info(TAG, 'case state has changed, new state is' + state);
+ switch (state) {
+ case 'idle':
+ this.recorderState = 'idle';
+ break;
+ case 'prepared':
+ this.recorderState = 'prepared';
+ break;
+ case 'started':
+ this.recorderState = 'started';
+ this.getRecordTime();
+ this.isStartRecording = true;
+ this.isPauseRecording = false;
+ break;
+ case 'paused':
+ this.recorderState = 'paused';
+ clearInterval(this.timer);
+ this.isPauseRecording = true;
+ break;
+ case 'stopped':
+ this.recorderState = 'stopped';
+ this.isStartRecording = false;
+ break;
+ case 'released':
+ this.recorderState = 'released';
+ break;
+ case 'error':
+ Logger.info(TAG, 'case error state!!!');
+ this.recorderState = 'error';
+ break;
+ default:
+ Logger.info(TAG, 'case start is unknown');
+ break;
+ }
+ });
+ this.audioRecorder.on('error', (err) => {
+ Logger.info(TAG, 'case avRecorder.on(error) called, errMessage is ' + err.message);
+ });
+ }
+ }
+
+ async prepareAudioRecorder(): Promise {
+ Logger.info(TAG, 'case prepareAudioRecorder in');
+ this.avConfig.profile.audioChannels = this.audioChannels;
+ this.avConfig.profile.audioSampleRate = this.audioSampleRate;
+ if (this.audioRecorder) {
+ await this.audioRecorder.prepare(this.avConfig).then(() => {
+ Logger.info(TAG, 'case prepare AVRecorder called');
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+ Logger.info(TAG, 'case prepareAudioRecorder out');
+ }
+
+ async startAudioRecording(): Promise {
+ Logger.info(TAG, 'case startAudioRecording called');
+ if (this.audioRecorder) {
+ await this.audioRecorder.start().then(() => {
+ Logger.info(TAG, 'case start AudioRecorder called');
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+ }
+
+ async pauseAudioRecording(): Promise {
+ Logger.info(TAG, 'case pauseAudioRecording called');
+ if (this.audioRecorder) {
+ await this.audioRecorder.pause().then(() => {
+ Logger.info(TAG, 'case pause AudioRecorder called');
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+ }
+
+ async resumeAudioRecording(): Promise {
+ Logger.info(TAG, 'case resumeAudioRecording called');
+ if (this.audioRecorder) {
+ await this.audioRecorder.resume().then(() => {
+ Logger.info(TAG, 'case resume AudioRecorder called');
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+ }
+
+ async stopAudioRecording(): Promise {
+ Logger.info(TAG, 'case stopAudioRecording called');
+ if (this.audioRecorder) {
+ await this.audioRecorder.stop().then(() => {
+ Logger.info(TAG, 'case stop AudioRecorder called');
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+ }
+
+ async resetAudioRecording(): Promise {
+ if (this.audioRecorder) {
+ await this.audioRecorder.reset().then(() => {
+ Logger.info(TAG, 'case resetAudioRecording called');
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+ }
+
+ async releaseAudioRecorder(): Promise {
+ if (this.audioRecorder) {
+ this.audioRecorder.off('stateChange');
+ this.audioRecorder.off('error');
+ await this.audioRecorder.release().then(() => {
+ Logger.info(TAG, 'case releaseAudioRecorder called');
+ }, this.failureCallback).catch(this.catchCallback);
+ this.audioRecorder = undefined;
+ }
+ }
+
+ // close file fd
+ async closeFd(): Promise {
+ Logger.info(TAG, 'case closeFd called');
+ if (this.mSaveCameraAsset) {
+ await this.mSaveCameraAsset.closeAudioFile();
+ this.mFileAssetId = undefined;
+ this.fdPath = undefined;
+ Logger.info(TAG, 'case closeFd done');
+ }
+ }
+
+ // start button process
+ async startRecordingProcess(): Promise {
+ Logger.info(TAG, 'startRecording called');
+ if (this.audioRecorder) {
+ Logger.info(TAG, 'audioRecorder exist,release it');
+ await this.audioRecorder.release();
+ }
+ await this.createAudioRecorder();
+ await this.getFileFd();
+ await this.prepareAudioRecorder();
+ await this.startAudioRecording();
+ Logger.info(TAG, 'startRecording done');
+ }
+
+ // pause button process
+ async pauseRecordingProcess(): Promise {
+ Logger.info(TAG, 'pauseRecording called');
+ if (this.recorderState === 'started') {
+ Logger.info(TAG, 'current state is started, to pause');
+ await this.pauseAudioRecording();
+ }
+ Logger.info(TAG, 'pauseRecording done');
+ }
+
+ // resume button process
+ async resumeRecordingProcess(): Promise {
+ Logger.info(TAG, 'resumeRecording called');
+ if (this.recorderState === 'paused') {
+ Logger.info(TAG, 'current state is paused, to resume');
+ await this.resumeAudioRecording();
+ }
+ Logger.info(TAG, 'resumeRecording done');
+ }
+
+ // stop button process
+ async stopRecordingProcess(): Promise {
+ Logger.info(TAG, 'stopRecording called');
+ if (this.recorderState === 'started' || this.recorderState === 'paused') {
+ await this.stopAudioRecording();
+ }
+ await this.resetAudioRecording();
+ await this.releaseAudioRecorder();
+ await this.closeFd();
+ clearInterval(this.timer);
+ this.isStartRecording = false;
+ this.isPauseRecording = false;
+ this.seconds = 0;
+ this.audioRecorderTimeText = '00:00';
+ Logger.info(TAG, 'stopRecording done');
+ }
+
+ build() {
+ Column() {
+ Row() {
+ Stack({ alignContent: Alignment.Center }) {
+ Text(this.audioRecorderTimeText)
+ .fontFamily('HarmonyHeiTi-Light')
+ .width(70)
+ .height(40)
+ .fontSize(27)
+ .fontColor('white')
+ .margin({ left: 99, top: 13, right: 79, bottom: 703 })
+ if (!this.isStartRecording) {
+ Image($r('app.media.ic_setting'))
+ .width(40)
+ .height(40)
+ .margin({ left: 12, top: 12, right: 308, bottom: 704 })
+ .onClick(() => {
+ if (this.settingDialog !== undefined) {
+ Logger.info(TAG, 'to open setting dialog');
+ this.settingDialog.open()
+ }
+ })
+ .id('audioSetting')
+ Image($r('app.media.ic_red_circle'))
+ .width(12)
+ .height(12)
+ .margin({ left: 136, top: 25, right: 212, bottom: 715 })
+ Image($r('app.media.take_video_normal'))
+ .width(86)
+ .height(86)
+ .margin({ left: 132, top: 612, right: 132, bottom: 72 })
+ .onClick(() => this.startRecordingProcess())
+ .id('audioStart')
+ }
+ else {
+ Image($r('app.media.ic_circle_open'))
+ .width(120)
+ .height(56)
+ .margin({ left: 120, top: 632, right: 120, bottom: 92 })
+ Image($r('app.media.ic_stop'))
+ .width(16)
+ .height(16)
+ .margin({ left: 146, top: 652, right: 198, bottom: 112 })
+ .onClick(() => this.stopRecordingProcess())
+ .id('audioStop')
+ if (!this.isPauseRecording) {
+ Image($r('app.media.ic_red_circle'))
+ .width(12)
+ .height(12)
+ .margin({ left: 136, top: 25, right: 212, bottom: 715 })
+ Image($r('app.media.ic_pause'))
+ .width(14)
+ .height(15)
+ .margin({ left: 200, top: 652, right: 146, bottom: 113 })
+ .onClick(() => this.pauseRecordingProcess())
+ .id('audioPause')
+ } else {
+ Image($r('app.media.ic_small_pause'))
+ .width(14)
+ .height(14)
+ .margin({ left: 136, top: 25, right: 212, bottom: 715 })
+ Image($r('app.media.ic_continue'))
+ .width(16)
+ .height(16)
+ .margin({ left: 198, top: 651, right: 144, bottom: 111 })
+ .onClick(() => this.resumeRecordingProcess())
+ .id('audioResume')
+ }
+ }
+ }.width('100%').height('100%')
+ }.backgroundColor(Color.Black)
+ }
+ }
+
+ onCancel() {
+ Logger.info(TAG, 'Callback when the cancel button is clicked')
+ }
+
+ onAccept() {
+ Logger.info(TAG, 'Callback when the confirm button is clicked')
+ }
+
+ existApp() {
+ Logger.info(TAG, 'Click the callback in the blank area')
+ }
+
+ settingDialog: CustomDialogController = new CustomDialogController({
+ builder: CustomDialogSetting({
+ cancel: this.onCancel,
+ confirm: this.onAccept,
+ audioChannels: $audioChannels,
+ audioSampleRate: $audioSampleRate,
+ }),
+ cancel: this.existApp,
+ autoCancel: false,
+ alignment: DialogAlignment.Bottom,
+ customStyle: true
+ })
+}
+
+
+@CustomDialog
+struct CustomDialogSetting {
+ @Link audioChannels: number
+ @Link audioSampleRate: number
+ controller: CustomDialogController
+ cancel?: () => void
+ confirm?: () => void
+ @State currentAudioChannels: number = 2
+ @State currentAudioSampleRate: number = 48000
+ private initAudioChannels: number = this.audioChannels
+ private initAudioSampleRate: number = this.audioSampleRate
+ @State audioChannelsList: Array = [1, 2]
+ @State audioSampleRateList: Array = [8000, 22050, 32000, 44100, 48000, 96000]
+
+ build() {
+ Column() {
+ Column() {
+ Text($r('app.string.audio_parameter'))
+ .fontFamily('HarmonyHeiTi-Medium')
+ .fontSize(20)
+ .width(288)
+ .height(26)
+ .margin({ left: 24, right: 24, top: 16 })
+ Column() {
+ List({ space: 10 }) {
+ ForEach(this.audioSampleRateList, (item: number, index) => {
+ ListItem() {
+ Row() {
+ Text($r('app.string.audio_samplerate'))
+ .fontSize(16)
+ Text(item.toString())
+ .fontSize(16)
+ .width(160)
+ if (item == this.initAudioSampleRate) {
+ Radio({ value: item.toString(), group: 'radioGroup2' }).checked(true)
+ .height(20)
+ .id(item.toString())
+ .onChange((isChecked: boolean) => {
+ if (isChecked) {
+ Logger.info(TAG, item.toString() + ' audioSample is chosen')
+ this.currentAudioSampleRate = item
+ }
+ })
+ } else {
+ Radio({ value: item.toString(), group: 'radioGroup2' }).checked(false)
+ .height(20)
+ .onChange((isChecked: boolean) => {
+ if (isChecked) {
+ Logger.info(TAG, item.toString() + ' audioSample is chosen')
+ this.currentAudioSampleRate = item
+ }
+ })
+ }
+ }.margin({ left: 0, right: 39 })
+ }
+ })
+ }.listDirection(Axis.Vertical)
+ .divider({ strokeWidth: 0.5, color: '#EEEEEE', startMargin: 0, endMargin: 0 })
+ }.margin({ left: 24, right: 24, top: 16 })
+ }.alignItems(HorizontalAlign.Start)
+
+ Row() {
+ Button($r('app.string.button_cancel'))
+ .onClick(() => {
+ this.audioChannels = this.initAudioChannels
+ this.audioSampleRate = this.initAudioSampleRate
+ this.controller.close()
+ if (this.cancel) {
+ this.cancel()
+ }
+ })
+ .backgroundColor($r('app.color.button_background'))
+ .fontColor($r('app.color.button_color'))
+ .fontFamily('HarmonyHeiTi-Medium')
+ .fontSize(16)
+ .id('audioCancel')
+ .layoutWeight(1)
+ Divider()
+ .vertical(true)
+ .height(24)
+ .color($r('app.color.divider_color'))
+ .opacity(0.6)
+ .margin({ left: 8, right: 8 })
+ Button($r('app.string.button_confirm'))
+ .onClick(() => {
+ this.audioChannels = this.currentAudioChannels
+ this.audioSampleRate = this.currentAudioSampleRate
+ this.controller.close()
+ if (this.confirm) {
+ this.confirm()
+ }
+ })
+ .backgroundColor($r('app.color.button_background'))
+ .fontColor($r('app.color.button_color'))
+ .fontFamily('HarmonyHeiTi-Medium')
+ .fontSize(16)
+ .id('audioConfirm')
+ .layoutWeight(1)
+ }
+ .width(300)
+ .height(40)
+ .margin({ top: 12, bottom: 16 })
+ }
+ .margin({ left: 12, right: 12, bottom: 12 })
+ .backgroundColor($r('app.color.button_background'))
+ .borderRadius(32)
+ .alignItems(HorizontalAlign.Center)
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/recorder/RemoteDeviceModel.ets b/test/hapAppDcameraSample/entry/src/main/ets/recorder/RemoteDeviceModel.ets
new file mode 100644
index 0000000000000000000000000000000000000000..5987d24c63409e7174764f419bb240acd8d8fffc
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/recorder/RemoteDeviceModel.ets
@@ -0,0 +1,275 @@
+/*
+ * Copyright (c) 2022 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 deviceManager from '@ohos.distributedDeviceManager';
+import Logger from '../utils/Logger'
+import { Callback } from '@ohos.base'
+
+interface deviceData {
+ device: deviceManager.DeviceBasicInfo
+}
+
+interface extraInfo {
+ bindType: number
+ targetPkgName: string
+ appName: string
+}
+
+const TAG: string = 'Sample_RemoteDeviceModel'
+let SUBSCRIBE_ID: number = 100
+
+export const BUNDLE_NAME: string = 'com.samples.avrecorder'
+
+export class RemoteDeviceModel {
+ public deviceList: Array | null = []
+ public discoverList: Array = []
+ private callback: () => void = () => {
+ }
+ private authCallback: () => void = () => {
+ }
+ private deviceManager: deviceManager.DeviceManager | undefined = undefined
+
+ registerDeviceListCallback(callback: Callback) {
+ Logger.info(TAG, `deviceManager type =${typeof (this.deviceManager)} ,${JSON.stringify(this.deviceManager)} ,${JSON.stringify(this.deviceManager) === '{}'}`)
+ if (typeof (this.deviceManager) !== 'undefined') {
+ this.registerDeviceListCallbackImplement(callback)
+ return
+ }
+ Logger.info(TAG, 'deviceManager.createDeviceManager begin')
+ try {
+ let dmInstance = deviceManager.createDeviceManager(BUNDLE_NAME);
+ this.deviceManager = dmInstance
+ this.registerDeviceListCallbackImplement(callback)
+ Logger.info(TAG, `createDeviceManager callback returned, value= ${JSON.stringify(this.deviceManager)}`)
+ } catch (error) {
+ Logger.error(TAG, `createDeviceManager throw code:${error.code} message:${error.message}`)
+ }
+ Logger.info(TAG, 'deviceManager.createDeviceManager end')
+ }
+
+ changeStateOnline(device: deviceManager.DeviceBasicInfo) {
+ if (this.deviceList !== null && !this.deviceList.some((existDevice) => existDevice.deviceId === device.deviceId)) {
+ this.deviceList![this.deviceList!.length] = device;
+ }
+ Logger.info(TAG, `online, device list= ${JSON.stringify(this.deviceList)}`);
+ this.callback();
+ if (this.authCallback !== null) {
+ this.authCallback();
+ this.authCallback = () => {
+ }
+ }
+ }
+
+ changeStateOffline(device: deviceManager.DeviceBasicInfo) {
+ if (this.deviceList !== null && this.deviceList!.length > 0) {
+ let list: Array = [];
+ for (let j = 0; j < this.deviceList!.length; j++) {
+ if (this.deviceList![j].deviceId !== device.deviceId) {
+ list[j] = device;
+ }
+ }
+ this.deviceList = list;
+ }
+ Logger.info(TAG, `offline, updated device list=${JSON.stringify(device)}`);
+ this.callback();
+ }
+
+ changeState(device: deviceManager.DeviceBasicInfo, state: number) {
+ if (this.deviceList !== null && this.deviceList!.length <= 0) {
+ this.callback();
+ return;
+ }
+ if (this.deviceList !== null && state === deviceManager.DeviceStateChange.AVAILABLE) {
+ let list: Array = new Array();
+ for (let i = 0; i < this.deviceList!.length; i++) {
+ if (this.deviceList![i].deviceId !== device.deviceId) {
+ list[i] = device;
+ }
+ }
+ this.deviceList = list;
+ Logger.info(TAG, `ready, device list= ${JSON.stringify(device)}`);
+ this.callback();
+ } else {
+ if (this.deviceList !== null) {
+ for (let j = 0; j < this.deviceList!.length; j++) {
+ if (this.deviceList![j].deviceId === device.deviceId) {
+ this.deviceList![j] = device;
+ break;
+ }
+ }
+ Logger.info(TAG, `offline, device list= ${JSON.stringify(this.deviceList)}`);
+ this.callback();
+ }
+ }
+ }
+
+ registerDeviceListCallbackImplement(callback: Callback) {
+ Logger.info(TAG, 'registerDeviceListCallback')
+ this.callback = callback
+ if (this.deviceManager === undefined) {
+ Logger.error(TAG, 'deviceManager has not initialized')
+ this.callback()
+ return
+ }
+ Logger.info(TAG, 'getTrustedDeviceListSync begin')
+ try {
+ let list = this.deviceManager !== undefined ? this.deviceManager.getAvailableDeviceListSync() : null;
+ Logger.info(TAG, `getTrustedDeviceListSync end, deviceList= ${JSON.stringify(list)}`);
+ if (typeof (list) !== 'undefined' && JSON.stringify(list) !== '[]') {
+ this.deviceList = list!;
+ }
+ Logger.info(TAG, `getTrustedDeviceListSync end, deviceList=${JSON.stringify(list)}`);
+ } catch (error) {
+ Logger.error(TAG, `getTrustedDeviceListSync throw code:${error.code} message:${error.message}`);
+ }
+ this.callback();
+ Logger.info(TAG, 'callback finished');
+ try {
+ if (this.deviceManager !== undefined) {
+ this.deviceManager.on('deviceStateChange', (data) => {
+ if (data === null) {
+ return
+ }
+ Logger.info(TAG, `deviceStateChange data= ${JSON.stringify(data)}`)
+ switch (data.action) {
+ case deviceManager.DeviceStateChange.AVAILABLE:
+ this.changeState(data.device, deviceManager.DeviceStateChange.AVAILABLE)
+ break
+ case deviceManager.DeviceStateChange.UNKNOWN:
+ this.changeStateOnline(data.device)
+ break
+ case deviceManager.DeviceStateChange.UNAVAILABLE:
+ this.changeStateOffline(data.device)
+ break
+ default:
+ break
+ }
+ })
+ }
+ if (this.deviceManager !== undefined) {
+ this.deviceManager.on('discoverSuccess', (data) => {
+ if (data === null) {
+ return
+ }
+ this.discoverList = []
+ Logger.info(TAG, `discoverSuccess data=${JSON.stringify(data)}`)
+ this.deviceFound(data.device)
+ })
+ this.deviceManager.on('discoverFailure', (data) => {
+ Logger.info(TAG, `discoverFailure data= ${JSON.stringify(data)}`)
+ })
+ this.deviceManager.on('serviceDie', () => {
+ Logger.error(TAG, 'serviceDie')
+ })
+ }
+ } catch (error) {
+ Logger.error(TAG, `on throw code:${error.code} message:${error.message}`)
+ }
+ this.startDeviceDiscovery()
+ }
+
+ deviceFound(data: deviceManager.DeviceBasicInfo) {
+ for (let i = 0;i < this.discoverList.length; i++) {
+ if (this.discoverList[i].deviceId === data.deviceId) {
+ Logger.info(TAG, 'device founded ignored')
+ return
+ }
+ }
+ this.discoverList[this.discoverList.length] = data
+ Logger.info(TAG, `deviceFound self.discoverList= ${this.discoverList}`)
+ this.callback()
+ }
+
+ /**
+ * 通过SUBSCRIBE_ID搜索分布式组网内的设备
+ */
+ startDeviceDiscovery() {
+ let discoverParam: Record = {
+ 'discoverTargetType': 1
+ };
+
+ let filterOptions: Record = {
+ 'availableStatus': 0,
+ };
+
+ Logger.info(TAG, `startDeviceDiscovery${SUBSCRIBE_ID}`);
+ try {
+ if (this.deviceManager !== undefined) {
+ this.deviceManager.startDiscovering(discoverParam, filterOptions)
+ }
+ } catch (error) {
+ Logger.error(TAG, `startDeviceDiscovery throw code:${error.code} message:${error.message}`)
+ }
+ }
+
+ unregisterDeviceListCallback() {
+ Logger.info(TAG, `stopDeviceDiscovery ${SUBSCRIBE_ID}`)
+ if (this.deviceManager === undefined) {
+ return
+ }
+ if (this.deviceManager !== undefined) {
+ try {
+ Logger.info(TAG, `stopDiscovering`)
+ this.deviceManager.stopDiscovering();
+ } catch (error) {
+ Logger.error(TAG, `stopDeviceDiscovery throw code:${JSON.stringify(error.code)} message:${error.message}`)
+ }
+ try {
+ this.deviceManager.off('deviceStateChange')
+ this.deviceManager.off('discoverSuccess')
+ this.deviceManager.off('discoverFailure')
+ this.deviceManager.off('serviceDie')
+ } catch (error) {
+ Logger.error(TAG, `off throw code:${error.code} message:${error.message}`)
+ }
+ }
+ this.deviceList = []
+ this.discoverList = []
+ }
+
+ authenticateDevice(device: deviceManager.DeviceBasicInfo, callBack: Callback) {
+ Logger.info(TAG, `authenticateDevice ${JSON.stringify(device)}`)
+ Logger.info(TAG, `authenticateDevice ${device.networkId}`)
+ for (let i = 0; i < this.discoverList.length; i++) {
+ if (this.discoverList[i].deviceId !== device.deviceId) {
+ continue
+ }
+ if (this.deviceManager === undefined) {
+ return
+ }
+ try {
+ if (this.deviceManager !== undefined) {
+ this.deviceManager.bindTarget(device.deviceId, {
+ bindLevel: 3,
+ bindType: 1,
+ targetPkgName: BUNDLE_NAME,
+ appName: 'Distributed distributecalc',
+ }, (err, data) => {
+ if (err) {
+ Logger.error(TAG, `authenticateDevice error: ${JSON.stringify(err)}`)
+ this.authCallback = () => {
+ }
+ return
+ }
+ Logger.info(TAG, `authenticateDevice succeed: ${JSON.stringify(data)}`)
+ this.authCallback = callBack
+ })
+ }
+ } catch (error) {
+ Logger.error(TAG, `authenticateDevice throw throw code:${error.code} message:${error.message}`)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/recorder/VideoRecorder.ets b/test/hapAppDcameraSample/entry/src/main/ets/recorder/VideoRecorder.ets
new file mode 100644
index 0000000000000000000000000000000000000000..ee7c93fe8388ccfffbb6ae17cda4f8e546ab0a9a
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/recorder/VideoRecorder.ets
@@ -0,0 +1,972 @@
+/*
+ * Copyright (C) 2023-2024 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 { camera } from '@kit.CameraKit';
+import { media } from '@kit.MediaKit';
+import { BusinessError } from '@kit.BasicServicesKit';
+import { dateTime } from '../utils/DateTimeUtils';
+import Logger from '../utils/Logger';
+import SaveCameraAsset from '../utils/SaveCameraAsset';
+import { RemoteDeviceModel } from './RemoteDeviceModel';
+import { TitleBarComponent } from '../common/TitleBarComponent'
+import json from '@ohos.util.json';
+import image from '@ohos.multimedia.image';
+import fileio from '@ohos.fileio';
+
+const TAG: string = 'Sample_VideoRecorder';
+
+@Entry
+@Component
+struct VideoRecording {
+ @State isLand: boolean = false
+ @State isShow: boolean = false
+ @State result: string = ''
+ private surfaceId: string = '';
+ xcomponentController: XComponentController = new XComponentController();
+ private videoRecorder?: media.AVRecorder;
+ private cameraManager?: camera.CameraManager;
+ private cameras?: Array;
+ private cameraInput?: camera.CameraInput;
+ private captureSession?: camera.CaptureSession;
+ private previewOutput?: camera.PreviewOutput;
+ private videoOutput?: camera.VideoOutput;
+ private videoOutSurfaceId: string = '';
+ private photoReceiver?: image.ImageReceiver;
+ private photoOutput?: camera.PhotoOutput;
+ private fdPath?: string = '';
+ @State videoRecorderTimeText: string = '00:00';
+ private seconds: number = 0;
+ private timer: number = 0;
+ private cameraIndex: number = 0;
+ @State videoResolution: Resolution = { frameWidth: 640, frameHeight: 480 };
+ @State framerate: number = 30; // set default video framerate 30
+ @State videoCodecType: string = 'video/avc';
+ private recorderState: string = 'free';
+ @State isStartRecording: boolean = false;
+ @State isPauseRecording: boolean = false;
+ private mSaveCameraAsset: SaveCameraAsset = new SaveCameraAsset(TAG);
+ private mFileAssetId?: number = 0;
+ private cameraOutputCapability?: camera.CameraOutputCapability;
+ private curVideoProfiles?: Array;
+ private previewProfiles?: Array;
+ private remoteDeviceModel: RemoteDeviceModel = new RemoteDeviceModel();
+
+ private avProfile: media.AVRecorderProfile = {
+ fileFormat: media.ContainerFormatType.CFT_MPEG_4, // set fileFormat, for video is mp4
+ videoBitrate: 3000000, // set videoBitrate according to device ability
+ videoCodec: media.CodecMimeType.VIDEO_AVC, // set videoCodec, avc or mpeg4 can be selected
+ videoFrameWidth: 640, // set videoFrameWidth according to device ability
+ videoFrameHeight: 480, // set videoFrameHeight according to device ability
+ videoFrameRate: 30 // set videoFrameRate according to device ability
+ }
+ private avConfig: media.AVRecorderConfig = {
+ videoSourceType: media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV,
+ profile: this.avProfile,
+ url: 'fd://',
+ }
+
+ aboutToAppear(): void {
+ Logger.info(TAG, 'aboutToAppear');
+ }
+
+ aboutToDisappear(): void {
+ Logger.info(TAG, 'aboutToDisappear called');
+ this.releaseCamera();
+ }
+
+ onPageShow(): void {
+ console.info(TAG, 'onPageShow called');
+ }
+
+ onPageHide(): void {
+ console.info(TAG, 'onPageHide called');
+ this.stopRecordingProcess();
+ }
+
+ async failureCallback(error: BusinessError): Promise {
+ Logger.info(TAG, 'case failureCallback called,errMessage is ', json.stringify(error));
+ }
+
+ async catchCallback(error: BusinessError): Promise {
+ Logger.info(TAG, 'case catchCallback called,errMessage is ', json.stringify(error));
+ }
+
+ // show recording time
+ getRecordTime(): void {
+ this.timer = setInterval(() => {
+ this.seconds += 1;
+ this.videoRecorderTimeText = dateTime(this.seconds);
+ }, 1000)
+ }
+
+ // get recording file fd
+ async getFileFd(): Promise {
+ Logger.info(TAG, 'getFileFd called');
+ this.mFileAssetId = await this.mSaveCameraAsset.createVideoFd();
+ this.fdPath = 'fd://' + this.mFileAssetId.toString();
+ this.avConfig.url = this.fdPath;
+ Logger.info(TAG, 'fdPath is: ' + this.fdPath);
+ Logger.info(TAG, 'getFileFd done');
+ }
+
+ async getRecordingFileFd(): Promise {
+ Logger.info(TAG, 'getRecordingFileFd called');
+ this.mFileAssetId = await this.mSaveCameraAsset.createVideoFd();
+ this.fdPath = 'fd://' + this.mFileAssetId.toString();
+ this.avConfig.url = this.fdPath;
+ Logger.info(TAG, 'fdPath is: ' + this.fdPath);
+ Logger.info(TAG, 'getFileFd done');
+ }
+
+ async getImageFileFd(): Promise {
+ Logger.info(TAG, 'getImageFileFd called');
+ this.mFileAssetId = await this.mSaveCameraAsset.createImageFd();
+ this.fdPath = 'fd://' + this.mFileAssetId.toString();
+ this.avConfig.url = this.fdPath;
+ Logger.info(TAG, 'ImageFileFd is: ' + this.fdPath);
+ Logger.info(TAG, 'getImageFileFd done');
+ }
+
+ // get camera ability
+ async initCamera(): Promise {
+ Logger.info(TAG, 'init camera called');
+ if (this.cameraManager) {
+ Logger.info(TAG, 'cameraManager already exits');
+ return;
+ }
+
+ Logger.info(TAG, '[camera] case to get cameraManager');
+ this.cameraManager = camera.getCameraManager(globalThis.abilityContext);
+ if (this.cameraManager) {
+ Logger.info(TAG, '[camera] case getCameraManager success');
+ } else {
+ Logger.info(TAG, '[camera] case getCameraManager failed');
+ return;
+ }
+
+ this.cameras = this.cameraManager.getSupportedCameras();
+ if (this.cameras) {
+ console.log(TAG, '[camera] case getCameras success, size ', this.cameras.length);
+ for (let i = 0; i < this.cameras.length; i++) {
+ let came: camera.CameraDevice = this.cameras[i];
+ console.log(TAG, '[came] camera json:', JSON.stringify(came));
+ if (came.connectionType == camera.ConnectionType.CAMERA_CONNECTION_REMOTE) {
+ this.cameraIndex = i;
+ this.cameraOutputCapability = this.cameraManager.getSupportedOutputCapability(came);
+ this.curVideoProfiles = this.cameraOutputCapability.videoProfiles;
+ Logger.info(TAG, 'init camera done');
+ break;
+ }
+ }
+ } else {
+ Logger.info(TAG, '[camera] case getCameras failed');
+ }
+ }
+
+ // create camera input
+ async createCameraInput(): Promise {
+ Logger.info(TAG, 'createCameraInput called');
+ if (this.cameras && this.cameras.length > 0) {
+ let came: camera.CameraDevice = this.cameras[this.cameraIndex];
+ console.log(TAG, '[came]createCameraInput camera json:', JSON.stringify(came));
+ this.cameraInput = this.cameraManager?.createCameraInput(came);
+ if (this.cameraInput) {
+ Logger.info(TAG, '[camera] case createCameraInput success');
+ await this.cameraInput.open().then(() => {
+ Logger.info(TAG, '[camera] case cameraInput.open() success');
+ }).catch((err: Error) => {
+ Logger.info(TAG, '[camera] cameraInput.open then.error:', json.stringify(err));
+ });
+ } else {
+ Logger.info(TAG, '[camera] case createCameraInput failed');
+ return;
+ }
+ }
+ }
+
+ // create camera preview
+ async createPreviewOutput(): Promise {
+ Logger.info(TAG, 'createPreviewOutput called');
+ if (this.cameraOutputCapability && this.cameraManager) {
+ this.previewProfiles = this.cameraOutputCapability.previewProfiles;
+ Logger.info(TAG, '[camera] this.previewProfiles json ', json.stringify(this.previewProfiles));
+ if (this.previewProfiles[0].format === camera.CameraFormat.CAMERA_FORMAT_YUV_420_SP) {
+ Logger.info(TAG, '[camera] case format is VIDEO_SOURCE_TYPE_SURFACE_YUV');
+ this.avConfig.videoSourceType = media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_YUV;
+ } else {
+ Logger.info(TAG, '[camera] case format is VIDEO_SOURCE_TYPE_SURFACE_ES');
+ this.avConfig.videoSourceType = media.VideoSourceType.VIDEO_SOURCE_TYPE_SURFACE_ES;
+ }
+ this.previewOutput = this.cameraManager.createPreviewOutput(this.previewProfiles[0], this.surfaceId);
+ if (!this.previewOutput) {
+ Logger.info(TAG, 'create previewOutput failed!');
+ }
+ Logger.info(TAG, 'createPreviewOutput done');
+ }
+ }
+
+ async createPhotoOutput() {
+ const photoProfile: camera.Profile = {
+ format: camera.CameraFormat.CAMERA_FORMAT_JPEG,
+ size: {
+ "width": 1280,
+ "height": 720
+ }
+ }
+ if (!this.cameraManager) {
+ Logger.error(TAG, 'createPhotoOutput cameraManager is null')
+ }
+ if (!this.photoReceiver) {
+ this.photoReceiver = image.createImageReceiver(photoProfile.size.width, photoProfile.size.height, photoProfile.format, 8)
+ this.photoReceiver.on("imageArrival",()=>{
+ this.photoReceiver?.readNextImage((err,image)=>{
+ if (err || image === undefined) {
+ Logger.error(TAG, 'photoReceiver imageArrival on error')
+ return
+ }
+ image.getComponent(4, async (err, img) => {
+ if (err || img === undefined) {
+ Logger.error(TAG, 'image getComponent on error')
+ return
+ }
+ await this.getImageFileFd()
+ fileio.write(this.mFileAssetId, img.byteBuffer)
+ await this.closeFd()
+ await image.release()
+ Logger.info(TAG, 'photoReceiver image.getComponent save success')
+ })
+ })
+ })
+ await this.photoReceiver.getReceivingSurfaceId().then((surfaceId: string) => {
+ this.photoOutput = this.cameraManager?.createPhotoOutput(photoProfile, surfaceId)
+ if (!this.photoOutput) {
+ Logger.error(TAG, 'cameraManager.createPhotoOutput on error')
+ }
+ Logger.info(TAG, 'cameraManager.createPhotoOutput success')
+ this.photoOutput?.on("captureStart", (err, captureId) => {
+ Logger.info(TAG, 'photoOutput.on captureStart')
+ })
+ }).catch((err: Error) => {
+ Logger.error(TAG, 'photoReceiver.getReceivingSurfaceId on error:' + err)
+ })
+ }
+ }
+
+ // create camera capture session
+ async createCaptureSession(): Promise {
+ Logger.info(TAG, 'createCaptureSession called');
+ if (this.cameraManager) {
+ this.captureSession = this.cameraManager.createCaptureSession();
+ if (!this.captureSession) {
+ Logger.info(TAG, 'createCaptureSession failed!');
+ return
+ }
+ try {
+ this.captureSession.beginConfig();
+ this.captureSession.addInput(this.cameraInput);
+ } catch (e) {
+ Logger.error(TAG, 'case addInput error:' + json.stringify(e));
+ }
+ try {
+ this.captureSession.addOutput(this.previewOutput);
+ } catch (e) {
+ Logger.error(TAG, 'case addOutput error:' + json.stringify(e));
+ }
+ await this.captureSession.commitConfig().then(() => {
+ Logger.info(TAG, 'captureSession commitConfig success');
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+ }
+
+ // start captureSession
+ async startCaptureSession(): Promise {
+ Logger.info(TAG, 'startCaptureSession called');
+ if (!this.captureSession) {
+ Logger.info(TAG, 'CaptureSession does not exists!');
+ return
+ }
+ await this.captureSession.start().then(() => {
+ Logger.info(TAG, 'case start captureSession success');
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+
+ // remove previous videoOutput
+ async removeVideoOutput(): Promise {
+ Logger.info(TAG, 'removeVideoOutput called');
+ if (this.videoOutput) {
+ Logger.info(TAG, 'videoOutput already exits, remove it');
+ if (this.captureSession) {
+ this.captureSession.removeOutput(this.videoOutput);
+ Logger.info(TAG, 'remove videoOutput done');
+ }
+ }
+ Logger.info(TAG, 'removeVideoOutput done');
+ }
+
+ // create video output
+ async createVideoOutput(): Promise {
+ Logger.info(TAG, 'createVideoOutput called');
+ if (this.videoRecorder && this.curVideoProfiles && this.cameraManager) {
+ this.curVideoProfiles[this.cameraIndex].size.height = this.videoResolution.frameHeight;
+ this.curVideoProfiles[this.cameraIndex].size.width = this.videoResolution.frameWidth;
+
+ Logger.info(TAG, '[createVideoOutput]' + this.curVideoProfiles[this.cameraIndex] + ":" + this.videoOutSurfaceId);
+
+ this.videoOutput = this.cameraManager.createVideoOutput(this.curVideoProfiles[this.cameraIndex], this.videoOutSurfaceId);
+ if (!this.videoOutput) {
+ Logger.info(TAG, '[camera] case create videoOutPut fail!!!');
+ return;
+ }
+ } else {
+ Logger.info(TAG, 'videoRecorder is null, createVideoOutput failed');
+ return;
+ }
+ }
+
+ // add video output into CaptureSession
+ async restartVideoOutput(): Promise {
+ Logger.info(TAG, 'restartVideoOutput called');
+ if (this.captureSession) {
+ await this.captureSession.stop().then(() => {
+ Logger.info(TAG, 'case stop captureSession success');
+ }, this.failureCallback).catch(this.catchCallback);
+ this.captureSession.beginConfig();
+ await this.removeVideoOutput();
+ await this.createVideoOutput();
+ this.captureSession.addOutput(this.videoOutput);
+ await this.captureSession.commitConfig().then(() => {
+ Logger.info(TAG, 'case commitConfig success');
+ }, this.failureCallback).catch(this.catchCallback);
+ await this.startCaptureSession();
+ if (this.videoOutput) {
+ await this.videoOutput.start().then(() => {
+ Logger.info(TAG, '[camera] case videoOutput start success');
+ }, this.failureCallback).catch(this.catchCallback);
+ } else {
+ Logger.info(TAG, 'videoOutput is null!!!');
+ return;
+ }
+ }
+ }
+
+ // start videoOutput
+ async startVideoOutput(): Promise {
+ Logger.info(TAG, 'startVideoOutput called');
+ if (this.videoOutput) {
+ await this.videoOutput.start().then(() => {
+ Logger.info(TAG, '[camera] case videoOutput start success');
+ }, this.failureCallback).catch(this.catchCallback);
+ } else {
+ Logger.info(TAG, 'videoOutput is null!!!');
+ return;
+ }
+ }
+
+ // stop videoOutput
+ async stopVideoOutput(): Promise {
+ Logger.info(TAG, 'stopVideoOutput called');
+ if (this.videoOutput) {
+ await this.videoOutput.stop().then(() => {
+ Logger.info(TAG, '[camera] case videoOutput stop success');
+ }, this.failureCallback).catch(this.catchCallback);
+ } else {
+ Logger.info(TAG, 'videoOutput is null!!!');
+ return;
+ }
+ }
+
+ // release camera
+ async releaseCameraInput(): Promise {
+ Logger.info(TAG, 'releaseCameraInput called');
+ if (this.cameraInput) {
+ this.cameraInput = undefined;
+ }
+ Logger.info(TAG, 'releaseCameraInput done');
+ }
+
+ // release preview
+ async releasePreviewOutput(): Promise {
+ Logger.info(TAG, 'releasePreviewOutput called');
+ if (this.previewOutput) {
+ await this.previewOutput.release().then(() => {
+ Logger.info('[camera] case main previewOutput release called');
+ }, this.failureCallback).catch(this.catchCallback);
+ this.previewOutput = undefined;
+ }
+ Logger.info(TAG, 'releasePreviewOutput done');
+ }
+
+ // release video output
+ async releaseVideoOutput(): Promise {
+ Logger.info(TAG, 'releaseVideoOutput called');
+ if (this.videoOutput) {
+ await this.videoOutput.release().then(() => {
+ Logger.info('[camera] case main videoOutput release called');
+ }, this.failureCallback).catch(this.catchCallback);
+ this.videoOutput = undefined;
+ }
+ Logger.info(TAG, 'releaseVideoOutput done');
+ }
+
+ // stop captureSession
+ async stopCaptureSession(): Promise {
+ Logger.info(TAG, 'stopCaptureSession called');
+ if (this.captureSession) {
+ await this.captureSession.stop().then(() => {
+ Logger.info('[camera] case main captureSession stop success');
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+ Logger.info(TAG, 'stopCaptureSession done');
+ }
+
+ // release captureSession
+ async releaseCaptureSession(): Promise {
+ Logger.info(TAG, 'releaseCaptureSession called');
+ if (this.captureSession) {
+ await this.captureSession.release().then(() => {
+ Logger.info('[camera] case main captureSession release success');
+ }, this.failureCallback).catch(this.catchCallback);
+ this.captureSession = undefined;
+ }
+ Logger.info(TAG, 'releaseCaptureSession done');
+ }
+
+ // clear camera environment
+ async releaseCamera(): Promise {
+ Logger.info(TAG, 'releaseCamera called');
+ await this.stopCaptureSession();
+ await this.releaseCameraInput();
+ await this.releasePreviewOutput();
+ await this.releaseVideoOutput();
+ await this.releaseCaptureSession();
+ Logger.info(TAG, 'releaseCamera done');
+ }
+
+ // set callback on
+ setCallback(): void {
+ Logger.info('case callback');
+ if (this.videoRecorder) {
+ this.videoRecorder.on('stateChange', (state, reason) => {
+ Logger.info('case state has changed, new state is' + state);
+ switch (state) {
+ case 'idle':
+ this.recorderState = 'idle';
+ break;
+ case 'prepared':
+ this.recorderState = 'prepared';
+ break;
+ case 'started':
+ this.recorderState = 'started';
+ this.getRecordTime();
+ this.isStartRecording = true;
+ this.isPauseRecording = false;
+ break;
+ case 'paused':
+ this.recorderState = 'paused';
+ clearInterval(this.timer);
+ this.isPauseRecording = true;
+ break;
+ case 'stopped':
+ this.recorderState = 'stopped';
+ this.isStartRecording = false;
+ break;
+ case 'released':
+ this.recorderState = 'released';
+ break;
+ case 'error':
+ Logger.info('case error state!!!');
+ this.recorderState = 'error';
+ break;
+ default:
+ Logger.info('case start is unknown');
+ break;
+ }
+ });
+ this.videoRecorder.on('error', (err) => {
+ Logger.info(TAG, 'case avRecorder.on(error) called, errMessage is ' + err.message);
+ });
+ }
+ }
+
+ async createVideoRecorder(): Promise {
+ await media.createAVRecorder().then((recorder) => {
+ Logger.info(TAG, 'case createAVRecorder called');
+ if (typeof (recorder) !== 'undefined') {
+ this.videoRecorder = recorder;
+ } else {
+ Logger.info(TAG, 'case create avRecorder failed!!!');
+ return
+ }
+ }, this.failureCallback).catch(this.catchCallback);
+ this.setCallback();
+ }
+
+ async prepareVideoRecorder(): Promise {
+ Logger.info(TAG, 'case prepareVideoRecorder in');
+ if (this.videoCodecType === 'video/mpeg4') {
+ Logger.info(TAG, 'VIDEO_MPEG4');
+ this.avConfig.profile.videoCodec = media.CodecMimeType.VIDEO_MPEG4;
+ } else {
+ Logger.info(TAG, 'VIDEO_AVC');
+ this.avConfig.profile.videoCodec = media.CodecMimeType.VIDEO_AVC;
+ }
+ this.avConfig.profile.videoCodec = media.CodecMimeType.VIDEO_AVC;
+ console.log(TAG, this.videoResolution.frameWidth, " ", this.videoResolution.frameHeight, " ", this.framerate);
+ this.avConfig.profile.videoFrameHeight = 480; //this.videoResolution.frameHeight;
+ this.avConfig.profile.videoFrameWidth = 640; //this.videoResolution.frameWidth;
+ this.avConfig.profile.videoFrameRate = this.framerate;
+ if (this.videoRecorder) {
+ await this.videoRecorder.prepare(this.avConfig).then(() => {
+ Logger.info(TAG, 'case prepare AVRecorder called');
+ }, this.failureCallback).catch(this.catchCallback);
+ Logger.info(TAG, 'case prepareVideoRecorder out');
+ }
+ }
+
+ async getInputSurface(): Promise {
+ Logger.info(TAG, 'case getInputSurface in');
+ if (this.videoRecorder) {
+ await this.videoRecorder.getInputSurface().then((outPutSurface) => {
+ Logger.info('case getInputSurface called');
+ if (!outPutSurface) {
+ Logger.error(TAG, 'case getInputSurface ID is none');
+ return
+ }
+ this.videoOutSurfaceId = outPutSurface;
+ Logger.info(TAG, 'case outPutSurface surfaceID is: ' + this.videoOutSurfaceId);
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+ Logger.info(TAG, 'case getInputSurface out');
+ }
+
+ async startVideoRecording(): Promise {
+ Logger.info(TAG, 'case startVideoRecording called');
+ if (this.videoRecorder) {
+ await this.videoRecorder.start().then(() => {
+ Logger.info(TAG, 'case start VideoRecorder called');
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+ }
+
+ async pauseVideoRecording(): Promise {
+ Logger.info(TAG, 'case pauseVideoRecording called');
+ if (this.videoRecorder) {
+ await this.videoRecorder.pause().then(() => {
+ Logger.info(TAG, 'case pause VideoRecorder called');
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+ }
+
+ async resumeVideoRecording(): Promise {
+ Logger.info(TAG, 'case resumeVideoRecording called');
+ if (this.videoRecorder) {
+ await this.videoRecorder.resume().then(() => {
+ Logger.info(TAG, 'case resume VideoRecorder called');
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+ }
+
+ async stopVideoRecording(): Promise {
+ Logger.info(TAG, 'case stopVideoRecording called');
+ if (this.videoRecorder) {
+ await this.videoRecorder.stop().then(() => {
+ Logger.info(TAG, 'case stop VideoRecorder called');
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+ }
+
+ async resetVideoRecording(): Promise {
+ if (this.videoRecorder) {
+ await this.videoRecorder.reset().then(() => {
+ Logger.info(TAG, 'case resetVideoRecording called');
+ }, this.failureCallback).catch(this.catchCallback);
+ }
+ }
+
+ async releaseVideoRecorder(): Promise {
+ if (this.videoRecorder) {
+ this.videoRecorder.off('stateChange');
+ this.videoRecorder.off('error');
+ await this.videoRecorder.release().then(() => {
+ Logger.info(TAG, 'case releaseVideoRecorder called');
+ }, this.failureCallback).catch(this.catchCallback);
+ this.videoRecorder = undefined;
+ }
+ }
+
+ // close file fd
+ async closeFd(): Promise {
+ Logger.info(TAG, 'case closeFd called');
+ if (this.mSaveCameraAsset) {
+ await this.mSaveCameraAsset.closeVideoFile();
+ this.mFileAssetId = undefined;
+ this.fdPath = undefined;
+ Logger.info(TAG, 'case closeFd done');
+ }
+ }
+
+ // init camera
+ async enterInit(): Promise {
+ Logger.info(TAG, 'enterInit called');
+ await this.initCamera(); // 获取设备相机信息
+ await this.createCameraInput(); // 打开相机
+ Logger.info(TAG, 'enterInit createPreviewOutput');
+ await this.createPreviewOutput(); // 预览流
+ // await this.createPhotoOutput(); // 拍照流
+ await this.createCaptureSession(); // 会话,保存一次相机运行需要的所有资源,并向相机设备申请完成相机功能
+ await this.startCaptureSession(); // 开启会话
+ Logger.info(TAG, 'end enterInit');
+ }
+
+ // start button process
+ async startRecordingProcess(): Promise {
+ Logger.info(TAG, 'startRecording called');
+ await this.createVideoRecorder();
+ await this.getFileFd();
+ await this.prepareVideoRecorder();
+ await this.getInputSurface();
+ await this.restartVideoOutput();
+ await this.startVideoRecording();
+ Logger.info(TAG, 'startRecording done');
+ }
+
+ // pause button process
+ async pauseRecordingProcess(): Promise {
+ Logger.info(TAG, 'pauseRecording called');
+ if (this.recorderState === 'started') {
+ Logger.info(TAG, 'current state is started, to pause');
+ await this.pauseVideoRecording();
+ await this.stopVideoOutput();
+ }
+ Logger.info(TAG, 'pauseRecording done');
+ }
+
+ // resume button process
+ async resumeRecordingProcess(): Promise {
+ Logger.info(TAG, 'resumeRecording called');
+ if (this.recorderState === 'paused') {
+ Logger.info(TAG, 'current state is paused, to resume');
+ await this.startVideoOutput();
+ await this.resumeVideoRecording();
+ }
+ Logger.info(TAG, 'resumeRecording done');
+ }
+
+ // stop button process
+ async stopRecordingProcess(): Promise {
+ Logger.info(TAG, 'stopRecording called');
+ if (this.recorderState === 'started' || this.recorderState === 'paused') {
+ await this.stopVideoRecording();
+ }
+ await this.resetVideoRecording();
+ await this.releaseVideoRecorder();
+ await this.stopVideoOutput();
+ this.videoOutput = undefined;
+ await this.closeFd();
+ clearInterval(this.timer);
+ this.isStartRecording = false;
+ this.isPauseRecording = false;
+ this.seconds = 0;
+ this.videoRecorderTimeText = '00:00';
+ Logger.info(TAG, 'stopRecording done');
+ }
+
+ startAbilityCallBack = (key: string) => {
+ Logger.info(TAG, `startAbilityCallBack ${key}`)
+ }
+ onInputValue = (value: string) => {
+ }
+
+ build() {
+ Column() {
+ Row() {
+ Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.SpaceBetween }) {
+ TitleBarComponent({
+ isLand: this.isLand,
+ startAbilityCallBack: this.startAbilityCallBack,
+ remoteDeviceModel: this.remoteDeviceModel,
+ isShow: this.isShow
+ })
+ Button("connect remote")
+ .onClick(() => {
+ this.enterInit();
+ })
+ }
+ }
+
+ Column() {
+ Row() {
+ Stack({ alignContent: Alignment.Center }) {
+ XComponent({
+ id: 'xcomponent1',
+ type: 'surface',
+ controller: this.xcomponentController
+ })
+ .onLoad(() => {
+ this.xcomponentController.setXComponentSurfaceSize({ surfaceWidth: 640, surfaceHeight: 480 })
+ this.surfaceId = this.xcomponentController.getXComponentSurfaceId()
+ Logger.info(TAG, '[VideoRecorder] surfaceId: ' + this.surfaceId)
+ Logger.info(TAG, 'XComponent1 loaded');
+ })
+ .width('100%').height('100%')
+ Text(this.videoRecorderTimeText)
+ .fontFamily('HarmonyHeiTi-Light')
+ .width(70)
+ .height(40)
+ .fontSize(27)
+ .fontColor(Color.Red)
+ .margin({
+ left: 155,
+ top: 18,
+ right: 135,
+ bottom: 707
+ })
+ .visibility(this.isStartRecording ? Visibility.Visible : Visibility.Hidden)
+ Image($r('app.media.take_video_normal'))
+ .width(86)
+ .height(86)
+ .margin({
+ left: 132,
+ top: 612,
+ right: 132,
+ bottom: 72
+ })
+ .onClick(() => this.startRecordingProcess())
+ .id('videoStart')
+ .visibility(this.isStartRecording ? Visibility.Hidden : Visibility.Visible)
+ Image($r('app.media.ic_circle_open'))
+ .width(120)
+ .height(56)
+ .margin({
+ left: 120,
+ top: 632,
+ right: 120,
+ bottom: 92
+ })
+ .visibility(this.isStartRecording ? Visibility.Visible : Visibility.Hidden)
+ Image($r('app.media.ic_stop'))
+ .width(16)
+ .height(16)
+ .margin({
+ left: 146,
+ top: 652,
+ right: 198,
+ bottom: 112
+ })
+ .onClick(() => this.stopRecordingProcess())
+ .id('videoStop')
+ .visibility(this.isStartRecording ? Visibility.Visible : Visibility.Hidden)
+ Image($r('app.media.ic_pause'))
+ .width(14)
+ .height(15)
+ .margin({
+ left: 200,
+ top: 652,
+ right: 146,
+ bottom: 113
+ })
+ .onClick(() => this.pauseRecordingProcess())
+ .id('videoPause')
+ .visibility(this.isStartRecording && !this.isPauseRecording ? Visibility.Visible : Visibility.Hidden)
+ Image($r('app.media.ic_continue'))
+ .width(16)
+ .height(16)
+ .margin({
+ left: 198,
+ top: 651,
+ right: 144,
+ bottom: 111
+ })
+ .onClick(() => this.resumeRecordingProcess())
+ .id('videoResume')
+ .visibility(this.isStartRecording && this.isPauseRecording ? Visibility.Visible : Visibility.Hidden)
+ }.width('100%').height('100%')
+ }.width('100%').height('88%')
+ }
+ }
+ }
+
+ onCancel() {
+ Logger.info(TAG, 'Callback when the cancel button is clicked')
+ }
+
+ onAccept() {
+ Logger.info(TAG, 'Callback when the confirm button is clicked')
+ }
+
+ existApp() {
+ Logger.info(TAG, 'Click the callback in the blank area')
+ }
+
+ settingDialog: CustomDialogController = new CustomDialogController({
+ builder: CustomDialogSetting({
+ cancel: this.onCancel,
+ confirm: this.onAccept,
+ videoResolution: $videoResolution,
+ videoFrameRate: $framerate,
+ videoCodec: $videoCodecType,
+ }),
+ cancel: this.existApp,
+ autoCancel: false,
+ alignment: DialogAlignment.Bottom,
+ customStyle: true
+ })
+}
+
+class Resolution {
+ public frameWidth: number = 0
+ public frameHeight: number = 0
+}
+
+class VideoResolution {
+ public itemValue: string = ''
+ public value: Resolution = { frameWidth: 0, frameHeight: 0 }
+}
+
+class VideoFrameRate {
+ public itemValue: string = ''
+ public value: number = 0
+}
+
+class VideoCodec {
+ public itemValue: string = ''
+ public value: string = ''
+}
+
+@CustomDialog
+struct CustomDialogSetting {
+ @Link videoResolution: Resolution;
+ @Link videoFrameRate: number;
+ @Link videoCodec: string;
+ controller: CustomDialogController;
+ cancel?: () => void;
+ confirm?: () => void;
+ @State currentVideoResolution: Resolution = { frameWidth: 640, frameHeight: 480 };
+ @State currentVideoResolutionText: string = '[16:9]1080p';
+ @State currentVideoFramerate: number = 30;
+ @State currentVideoCodec: string = 'video/avc';
+ private initVideoResolution: Resolution = this.videoResolution;
+ private initVideoFramerate: number = this.videoFrameRate;
+ private initVideoCodec: string = 'video/avc';
+ @State videoResolutionList: Array = [
+ { itemValue: '16:9 (720p)', value: { frameWidth: 1280, frameHeight: 720 } },
+ { itemValue: '16:9 (1080p)', value: { frameWidth: 640, frameHeight: 480 } },
+ ];
+ @State videoFrameRateList: Array = [
+ { itemValue: '30fps', value: 30 },
+ ];
+ @State videoCodecList: Array = [
+ { itemValue: 'video/avc', value: 'video/avc' },
+ { itemValue: 'video/mpeg4', value: 'video/mpeg4' },
+ ];
+
+ build() {
+ Column() {
+ Column() {
+ Text($r('app.string.video_parameter'))
+ .fontFamily('HarmonyHeiTi-Medium')
+ .fontSize(20)
+ .width(288)
+ .height(26)
+ .margin({ left: 24, top: 16, right: 24 })
+ Column() {
+ List({ space: 10 }) {
+ ForEach(this.videoResolutionList, (item: VideoResolution, index) => {
+ ListItem() {
+ Row() {
+ Text($r('app.string.video_resolution'))
+ .fontSize(16)
+ Text(item.itemValue.toString())
+ .fontSize(16)
+ .width(160)
+ .fontFamily('HarmonyHeiTi-Medium')
+ Radio({ value: item.value.toString(), group: 'radioGroup' })
+ .checked(item.value.frameWidth == this.initVideoResolution.frameWidth)
+ .height(24)
+ .onChange((isChecked: boolean) => {
+ if (isChecked) {
+ Logger.info(TAG, item.itemValue + ' resolution is clicked')
+ this.currentVideoResolution = item.value;
+ this.currentVideoResolutionText = item.itemValue;
+ }
+ })
+ }.margin({ left: 0, right: 39 })
+ }
+ })
+ }.listDirection(Axis.Vertical)
+ .divider({
+ strokeWidth: 0.5,
+ color: '#EEEEEE',
+ startMargin: 0,
+ endMargin: 0
+ })
+ }.margin({ left: 24, right: 24, top: 16 })
+ }.alignItems(HorizontalAlign.Start)
+
+ Row() {
+ Button($r('app.string.button_cancel'))
+ .onClick(() => {
+ this.videoResolution = this.initVideoResolution
+ this.videoFrameRate = this.initVideoFramerate
+ this.videoCodec = this.initVideoCodec
+ this.controller.close()
+ if (this.cancel) {
+ this.cancel()
+ }
+ })
+ .backgroundColor($r('app.color.button_background'))
+ .fontColor($r('app.color.button_color'))
+ .fontFamily('HarmonyHeiTi-Medium')
+ .fontSize(16)
+ .id('videoCancel')
+ .layoutWeight(1)
+ Divider()
+ .vertical(true)
+ .height(24)
+ .color($r('app.color.divider_color'))
+ .opacity(0.6)
+ .margin({ left: 8, right: 8 })
+ Button($r('app.string.button_confirm'))
+ .onClick(() => {
+ this.videoResolution = this.currentVideoResolution
+ this.videoFrameRate = this.currentVideoFramerate
+ this.videoCodec = this.currentVideoCodec
+ this.controller.close()
+ if (this.confirm) {
+ this.confirm()
+ }
+ })
+ .backgroundColor($r('app.color.button_background'))
+ .fontColor($r('app.color.button_color'))
+ .fontFamily('HarmonyHeiTi-Medium')
+ .fontSize(16)
+ .id('videoConfirm')
+ .layoutWeight(1)
+ }
+ .width(300)
+ .height(40)
+ .margin({
+ left: 24,
+ right: 24,
+ top: 12,
+ bottom: 16
+ })
+ }
+ .margin({ left: 12, right: 12, bottom: 12 })
+ .backgroundColor($r('app.color.button_background'))
+ .borderRadius(32)
+ .alignItems(HorizontalAlign.Center)
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/utils/DateTimeUtils.ets b/test/hapAppDcameraSample/entry/src/main/ets/utils/DateTimeUtils.ets
new file mode 100644
index 0000000000000000000000000000000000000000..76c5c325f2e7a84a5a3bc1aa8a61ada0f7d17664
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/utils/DateTimeUtils.ets
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2023-2024 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.
+ */
+
+export default class DateTimeUtil {
+ getTime(): string {
+ const DATETIME = new Date();
+ return this.concatTime(DATETIME.getHours(), DATETIME.getMinutes(), DATETIME.getSeconds())
+ }
+
+ getDate(): string {
+ const DATETIME = new Date();
+ return this.concatDate(DATETIME.getFullYear(), DATETIME.getMonth() + 1, DATETIME.getDate())
+ }
+
+ fill(value: number): string {
+ return (value > 9 ? '' : '0') + value;
+ }
+
+ concatDate(year: number, month: number, date: number): string {
+ return `${year}${month}${date}`;
+ }
+
+ concatTime(hour: number, minute: number, second: number): string {
+ return `${this.fill(hour)}${this.fill(minute)}${this.fill(second)}`;
+ }
+}
+
+export function getShownTimer(ms: number): string {
+ let seconds: number = Math.round(ms / 1000);
+ let sec: number = seconds % 60;
+ let min: number = (seconds - sec) / 60;
+ if (sec < 10) {
+ sec = 0 + sec;
+ }
+ if (min < 10) {
+ min = 0 + min;
+ }
+ return min + ':' + sec;
+}
+
+export function dateTime(t: number): string {
+ let minute: number = Math.floor(t / 60) % 60
+ let m = minute < 10 ? '0' + minute : minute
+ let second: number = t % 60
+ let s = second < 10 ? '0' + second : second
+ return m + ':' + s;
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/utils/ImageList.ets b/test/hapAppDcameraSample/entry/src/main/ets/utils/ImageList.ets
new file mode 100644
index 0000000000000000000000000000000000000000..7471f04073a8c66f3f61a281181b81f298ef51b3
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/utils/ImageList.ets
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2022 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.
+ */
+export class ImageList {
+ image: Resource
+ value: string
+
+ constructor(image: Resource, value: string) {
+ this.image = image
+ this.value = value
+ }
+}
+
+export function obtainImgVertical(): Record[] {
+ let list: Record[] =
+ [
+ { 'image': $r('app.media.ic_cal_seven'), 'value': '7' },
+ { 'image': $r('app.media.ic_cal_eight'), 'value': '8' },
+ { 'image': $r('app.media.ic_cal_nine'), 'value': '9' },
+ { 'image': $r('app.media.ic_cal_four'), 'value': '4' },
+ { 'image': $r('app.media.ic_cal_five'), 'value': '5' },
+ { 'image': $r('app.media.ic_cal_six'), 'value': '6' },
+ { 'image': $r('app.media.ic_cal_one'), 'value': '1' },
+ { 'image': $r('app.media.ic_cal_two'), 'value': '2' },
+ { 'image': $r('app.media.ic_cal_three'), 'value': '3' },
+ { 'image': $r('app.media.ic_cal_zero'), 'value': '0' },
+ { 'image': $r('app.media.ic_cal_point'), 'value': '.' },
+ ]
+ return list
+}
+
+export function obtainImgV(): Record[] {
+ let list: Record[] =
+ [
+ { 'image': $r('app.media.ic_cal_delete'), 'value': 'D' },
+ { 'image': $r('app.media.ic_cal_delete_c'), 'value': 'C' },
+ { 'image': $r('app.media.ic_cal_equal'), 'value': '=' }
+ ];
+ return list;
+}
+
+export function obtainImgVs(): Record[] {
+ let list: Record[] =
+ [
+ { 'image': $r('app.media.ic_cal_devide'), 'value': '/' },
+ { 'image': $r('app.media.ic_cal_multiply'), 'value': '*' },
+ { 'image': $r('app.media.ic_cal_minus'), 'value': '-' },
+ { 'image': $r('app.media.ic_cal_plus'), 'value': '+' }
+ ];
+ return list;
+}
+
+export function obtainImgLevel(): Record[] {
+ let list: Record[] = [
+ { 'image': $r('app.media.ic_cal_seven'), 'value': '7' },
+ { 'image': $r('app.media.ic_cal_eight'), 'value': '8' },
+ { 'image': $r('app.media.ic_cal_nine'), 'value': '9' },
+ { 'image': $r('app.media.ic_cal_devide'), 'value': '/' },
+ { 'image': $r('app.media.ic_cal_four'), 'value': '4' },
+ { 'image': $r('app.media.ic_cal_five'), 'value': '5' },
+ { 'image': $r('app.media.ic_cal_six'), 'value': '6' },
+ { 'image': $r('app.media.ic_cal_multiply'), 'value': '*' },
+ { 'image': $r('app.media.ic_cal_one'), 'value': '1' },
+ { 'image': $r('app.media.ic_cal_two'), 'value': '2' },
+ { 'image': $r('app.media.ic_cal_three'), 'value': '3' },
+ { 'image': $r('app.media.ic_cal_minus'), 'value': '-' },
+ { 'image': $r('app.media.ic_cal_zero'), 'value': '0' },
+ { 'image': $r('app.media.ic_cal_point'), 'value': '.' },
+ { 'image': $r('app.media.ic_cal_plus'), 'value': '+' }
+ ]
+ return list
+}
+
+export function obtainImg(): Record[] {
+ let list: Record[] = [
+ { 'image': $r('app.media.ic_cal_delete'), 'value': 'D' },
+ { 'image': $r('app.media.ic_cal_delete_c'), 'value': 'C' },
+ { 'image': $r('app.media.ic_cal_equal'), 'value': '=' }
+ ];
+ return list;
+}
+
+export function obtainImgL(): Record[] {
+ let list: Record[] = [
+ { 'image': $r('app.media.ic_cal_zero'), 'value': '0' },
+ { 'image': $r('app.media.ic_cal_point'), 'value': '.' },
+ { 'image': $r('app.media.ic_cal_plus'), 'value': '+' }
+ ]
+ return list
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/utils/Logger.ts b/test/hapAppDcameraSample/entry/src/main/ets/utils/Logger.ts
new file mode 100644
index 0000000000000000000000000000000000000000..1c03ba768c0d934308d4b8282207bbb18131d4a9
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/utils/Logger.ts
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2023-2024 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 hilog from '@ohos.hilog';
+
+class Logger {
+ private domain: number;
+ private prefix: string;
+ private format: string = '%{public}s, %{public}s';
+
+ constructor(prefix: string) {
+ this.prefix = prefix;
+ this.domain = 0xFF00;
+ }
+
+ debug(...args: string[]): void {
+ hilog.debug(this.domain, this.prefix, this.format, args);
+ }
+
+ info(...args: string[]): void {
+ hilog.info(this.domain, this.prefix, this.format, args);
+ }
+
+ warn(...args: string[]): void {
+ hilog.warn(this.domain, this.prefix, this.format, args);
+ }
+
+ error(...args: string[]): void {
+ hilog.error(this.domain, this.prefix, this.format, args);
+ }
+}
+
+export default new Logger('AVRecorderSample');
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/ets/utils/SaveCameraAsset.ets b/test/hapAppDcameraSample/entry/src/main/ets/utils/SaveCameraAsset.ets
new file mode 100644
index 0000000000000000000000000000000000000000..ee3d871d4074a8386e593f8d868f5442b3d88468
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/ets/utils/SaveCameraAsset.ets
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2023-2024 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 { common } from '@kit.AbilityKit';
+import { fileIo as fs } from '@kit.CoreFileKit';
+import DateTimeUtil from './DateTimeUtils';
+import Logger from './Logger';
+import { photoAccessHelper } from '@kit.MediaLibraryKit';
+import { dataSharePredicates } from '@kit.ArkData';
+
+export default class SaveCameraAsset {
+ private tag: string
+
+ constructor(tag: string) {
+ this.tag = tag;
+ }
+
+ private context = getContext(this) as common.UIAbilityContext;
+ private lastSaveTime: string = '';
+ private saveIndex: number = 0;
+ public VideoPrepareFile?: fs.File;
+ public AudioPrepareFile?: fs.File;
+ public ImagePrepareFile?: fs.File;
+
+ public async createImageFd() {
+ Logger.info(this.tag, 'get Image File Fd');
+ const mDateTimeUtil = new DateTimeUtil();
+ const displayName = this.checkName(`REC_${mDateTimeUtil.getDate()}_${mDateTimeUtil.getTime()}`) + '.jpg';
+ Logger.info(this.tag, 'get Image display name is: ' + displayName);
+ this.ImagePrepareFile = fs.openSync(this.context.filesDir + '/' + displayName, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
+ let fdNumber: number = this.ImagePrepareFile.fd;
+ Logger.info(this.tag, 'get Image File fd is: ' + JSON.stringify(fdNumber));
+ return fdNumber;
+ }
+
+ public async closeImageFile() {
+ if (this.ImagePrepareFile) {
+ await fs.close(this.ImagePrepareFile);
+ Logger.info(this.tag, 'close Image File end');
+ }
+ }
+
+ public async createVideoFd() {
+ // const context = getContext(this);
+ // let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
+ // let albumPredicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
+ // albumPredicates.equalTo(photoAccessHelper.AlbumKeys.ALBUM_NAME, 'albumName');
+ // let albumFetchOptions: photoAccessHelper.FetchOptions = {
+ // fetchColumns: [],
+ // predicates: albumPredicates
+ // };
+ //
+ // let photoPredicates: dataSharePredicates.DataSharePredicates = new dataSharePredicates.DataSharePredicates();
+ // let photoFetchOptions: photoAccessHelper.FetchOptions = {
+ // fetchColumns: [],
+ // predicates: photoPredicates
+ // };
+ //
+ // let photoFetchResult: photoAccessHelper.FetchResult
+ // let albumFetchResult: photoAccessHelper.FetchResult
+ //
+ // try {
+ // photoFetchResult = await phAccessHelper.getAssets(photoFetchOptions);
+ // let photoAsset: photoAccessHelper.PhotoAsset = await photoFetchResult.getFirstObject();
+ // console.info('getAssets successfully, albumName: ' + photoAsset.displayName);
+ //
+ // let uri: string = await phAccessHelper.createAsset(photoAccessHelper.PhotoType.VIDEO, "mp4", (err, uri) => {
+ //
+ // })
+ //
+ // albumFetchResult =
+ // await phAccessHelper.getAlbums(photoAccessHelper.AlbumType.USER, photoAccessHelper.AlbumSubtype.USER_GENERIC,
+ // albumFetchOptions);
+ // let album: photoAccessHelper.Album = await albumFetchResult.getFirstObject();
+ // console.info('getAlbums successfully, albumName: ' + album.albumName);
+ // let albumChangeRequest: photoAccessHelper.MediaAlbumChangeRequest =
+ // new photoAccessHelper.MediaAlbumChangeRequest(album);
+ // albumChangeRequest.addAssets([photoAsset]);
+ // await phAccessHelper.applyChanges(albumChangeRequest);
+ // console.info('succeed to add ' + photoAsset.displayName + ' to ' + album.albumName);
+ //
+ // } catch (err) {
+ // console.error('addAssets failed with err: ' + err);
+ // } finally {
+ // photoFetchResult!.close();
+ // albumFetchResult!.close();
+ // }
+
+ Logger.info(this.tag, 'get Recorder File Fd');
+ const mDateTimeUtil = new DateTimeUtil();
+ const displayName = this.checkName(`REC_${mDateTimeUtil.getDate()}_${mDateTimeUtil.getTime()}`) + '.mp4';
+ Logger.info(this.tag, 'get Recorder display name is: ' + displayName);
+ this.VideoPrepareFile = fs.openSync(this.context.filesDir + '/' + displayName, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
+ let fdNumber: number = this.VideoPrepareFile.fd;
+ Logger.info(this.tag, 'get Recorder File fd is: ' + JSON.stringify(fdNumber));
+ return fdNumber;
+ }
+
+ // public async createVideoFd() {
+ // Logger.info(this.tag, 'get Recorder File Fd');
+ // const mDateTimeUtil = new DateTimeUtil();
+ // const displayName = this.checkName(`REC_${mDateTimeUtil.getDate()}_${mDateTimeUtil.getTime()}`) + '.mp4';
+ // Logger.info(this.tag, 'get Recorder display name is: ' + displayName);
+ // this.VideoPrepareFile = fs.openSync(this.context.filesDir + '/' + displayName, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
+ // let fdNumber: number = this.VideoPrepareFile.fd;
+ // Logger.info(this.tag, 'get Recorder File fd is: ' + JSON.stringify(fdNumber));
+ // return fdNumber;
+ // }
+
+ public async closeVideoFile() {
+ if (this.VideoPrepareFile) {
+ await fs.close(this.VideoPrepareFile);
+ Logger.info(this.tag, 'close Video File end');
+ }
+ }
+
+ public async createAudioFd() {
+ Logger.info(this.tag, 'get Recorder File Fd');
+ const mDateTimeUtil = new DateTimeUtil();
+ const displayName = this.checkName(`REC_${mDateTimeUtil.getDate()}_${mDateTimeUtil.getTime()}`) + '.wav';
+ Logger.info(this.tag, 'get Recorder display name is: ' + displayName);
+ this.AudioPrepareFile = fs.openSync(this.context.filesDir + '/' + displayName, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
+ let fdNumber: number = this.AudioPrepareFile.fd;
+ Logger.info(this.tag, 'get Recorder File fd is: ' + JSON.stringify(fdNumber));
+ return fdNumber;
+ }
+
+ public async closeAudioFile() {
+ if (this.AudioPrepareFile) {
+ await fs.close(this.AudioPrepareFile);
+ Logger.info(this.tag, 'close Audio File end');
+ }
+ }
+
+ private checkName(name: string): string {
+ if (this.lastSaveTime == name) {
+ this.saveIndex += 1;
+ return `${name}_${this.saveIndex}`
+ }
+ this.lastSaveTime = name;
+ this.saveIndex = 0;
+ Logger.info(this.tag, 'get Recorder File name is: ' + name);
+ return name;
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/module.json5 b/test/hapAppDcameraSample/entry/src/main/module.json5
new file mode 100644
index 0000000000000000000000000000000000000000..75b6546c11eae6e19dd4296e58478d077683a704
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/module.json5
@@ -0,0 +1,137 @@
+/*
+ * 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.
+ */
+{
+ "module": {
+ "name": "entry",
+ "type": "entry",
+ "description": "$string:module_desc",
+ "mainElement": "EntryAbility",
+ "deviceTypes": [
+ "phone",
+ "tablet",
+ "2in1"
+ ],
+ "deliveryWithInstall": true,
+ "installationFree": false,
+ "pages": "$profile:main_pages",
+ "abilities": [
+ {
+ "name": "EntryAbility",
+ "srcEntry": "./ets/entryability/EntryAbility.ts",
+ "description": "$string:EntryAbility_desc",
+ "icon": "$media:layered_image",
+ "label": "$string:EntryAbility_label",
+ "startWindowIcon": "$media:startIcon",
+ "startWindowBackground": "$color:start_window_background",
+ "exported": true,
+ "skills": [
+ {
+ "entities": [
+ "entity.system.home"
+ ],
+ "actions": [
+ "action.system.home"
+ ]
+ }
+ ]
+ }
+ ],
+ "requestPermissions": [
+ {
+ "name": 'ohos.permission.DISTRIBUTED_DATASYNC',
+ "reason": "$string:reason",
+ "usedScene": {
+ "abilities": [
+ "EntryAbility"
+ ],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": 'ohos.permission.USE_BLUETOOTH',
+ "reason": '$string:reason'
+ },
+ {
+ "name": 'ohos.permission.DISCOVER_BLUETOOTH',
+ "reason": '$string:reason'
+ },
+ {
+ "name": "ohos.permission.MEDIA_LOCATION",
+ "reason": "$string:reason",
+ "usedScene": {
+ "abilities": [
+ "EntryAbility"
+ ],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": "ohos.permission.READ_MEDIA",
+ "reason": "$string:reason",
+ "usedScene": {
+ "abilities": [
+ "EntryAbility"
+ ],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": "ohos.permission.WRITE_MEDIA",
+ "reason": "$string:reason",
+ "usedScene": {
+ "abilities": [
+ "EntryAbility"
+ ],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": "ohos.permission.CAMERA",
+ "reason": "$string:reason",
+ "usedScene": {
+ "abilities": [
+ "EntryAbility"
+ ],
+ "when": "inuse"
+ }
+ },
+ {
+ "name": "ohos.permission.MICROPHONE",
+ "reason": "$string:reason",
+ "usedScene": {
+ "abilities": [
+ "EntryAbility"
+ ],
+ "when": "inuse"
+ }
+ }
+ ],
+ "extensionAbilities": [
+ {
+ "name": "EntryBackupAbility",
+ "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets",
+ "type": "backup",
+ "exported": false,
+ "metadata": [
+ {
+ "name": "ohos.extension.backup",
+ "resource": "$profile:backup_config"
+ }
+ ],
+ }
+ ],
+ "deliveryWithInstall": true
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/element/color.json b/test/hapAppDcameraSample/entry/src/main/resources/base/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..0d98b5c17780f121191f2768bdf0cd0bc6e9b9e7
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/element/color.json
@@ -0,0 +1,28 @@
+{
+ "color": [
+ {
+ "name": "start_window_background",
+ "value": "#FFFFFF"
+ },
+ {
+ "name": "button_background",
+ "value": "#FFFFFF"
+ },
+ {
+ "name": "homepage_background",
+ "value": "#F1F3F5"
+ },
+ {
+ "name": "title_color",
+ "value": "#000000"
+ },
+ {
+ "name": "button_color",
+ "value": "#007DFF"
+ },
+ {
+ "name": "divider_color",
+ "value": "#182431"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/element/string.json b/test/hapAppDcameraSample/entry/src/main/resources/base/element/string.json
new file mode 100644
index 0000000000000000000000000000000000000000..81a79fefaf43bb5907e974fcf86bccfd8c552a3d
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/element/string.json
@@ -0,0 +1,84 @@
+{
+ "string": [
+ {
+ "name": "module_desc",
+ "value": "This module template implements List functions."
+ },
+ {
+ "name": "ability_desc",
+ "value": "This ability loads ListPage"
+ },
+ {
+ "name": "ability_label",
+ "value": "List Ability"
+ },
+ {
+ "name": "page_title",
+ "value": "Title"
+ },
+ {
+ "name": "page_title_video",
+ "value": "VideoRecorder"
+ },
+ {
+ "name": "page_title_audio",
+ "value": "AudioRecorder"
+ },
+ {
+ "name": "button_confirm",
+ "value": "confirm"
+ },
+ {
+ "name": "button_cancel",
+ "value": "cancel"
+ },
+ {
+ "name": "audio_parameter",
+ "value": "Select audio samplerate"
+ },
+ {
+ "name": "audio_samplerate",
+ "value": "audio samplerate: "
+ },
+ {
+ "name": "video_parameter",
+ "value": "Select video resolution"
+ },
+ {
+ "name": "video_resolution",
+ "value": "video resolution: "
+ },
+ {
+ "name": "reason",
+ "value": "Permission"
+ },
+ {
+ "name": "choiceDevice",
+ "value": "Choice Device"
+ },
+ {
+ "name": "cancel",
+ "value": "Cancel"
+ },
+ {
+ "name": "distributed_calculator",
+ "value": "distributed calculator"
+ },
+ {
+ "name": "distributed_permission",
+ "value": "Allow data exchange between different devices"
+ },
+ {
+ "name": "localhost",
+ "value": "localhost"
+ },
+ {
+ "name": "EntryAbility_desc",
+ "value": "EntryAbility"
+ },
+ {
+ "name": "EntryAbility_label",
+ "value": "EntryAbility"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/background.png b/test/hapAppDcameraSample/entry/src/main/resources/base/media/background.png
new file mode 100644
index 0000000000000000000000000000000000000000..f939c9fa8cc8914832e602198745f592a0dfa34d
Binary files /dev/null and b/test/hapAppDcameraSample/entry/src/main/resources/base/media/background.png differ
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/checked.png b/test/hapAppDcameraSample/entry/src/main/resources/base/media/checked.png
new file mode 100644
index 0000000000000000000000000000000000000000..a77ded514ac884365ec515801bb34c68e6e5b7f8
Binary files /dev/null and b/test/hapAppDcameraSample/entry/src/main/resources/base/media/checked.png differ
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/equal.png b/test/hapAppDcameraSample/entry/src/main/resources/base/media/equal.png
new file mode 100644
index 0000000000000000000000000000000000000000..9fc25a3e16bdde93933d4bb2571631e1d382f1fb
Binary files /dev/null and b/test/hapAppDcameraSample/entry/src/main/resources/base/media/equal.png differ
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/foreground.png b/test/hapAppDcameraSample/entry/src/main/resources/base/media/foreground.png
new file mode 100644
index 0000000000000000000000000000000000000000..4483ddad1f079e1089d685bd204ee1cfe1d01902
Binary files /dev/null and b/test/hapAppDcameraSample/entry/src/main/resources/base/media/foreground.png differ
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_back.png b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_back.png
new file mode 100644
index 0000000000000000000000000000000000000000..4a269a11a6eca005d2eabb957992297446c858db
Binary files /dev/null and b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_back.png differ
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_delete.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_delete.svg
new file mode 100644
index 0000000000000000000000000000000000000000..34cd7762965b6bdbd920ccd6bc1cd17028aabc11
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_delete.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_delete_c.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_delete_c.svg
new file mode 100644
index 0000000000000000000000000000000000000000..d251a289d7710cadb6df00b1d9e6cf9b9dbd22f2
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_delete_c.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_devide.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_devide.svg
new file mode 100644
index 0000000000000000000000000000000000000000..6a24531c8ea11291ed310d302c711e61d63d2d7f
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_devide.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_eight.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_eight.svg
new file mode 100644
index 0000000000000000000000000000000000000000..9da3b65c0a15ae6036592c86466724b8314b0f5d
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_eight.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_equal.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_equal.svg
new file mode 100644
index 0000000000000000000000000000000000000000..8ff483b2e53c808b9f485ee2f6f99084f90b7ffc
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_equal.svg
@@ -0,0 +1,26 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_five.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_five.svg
new file mode 100644
index 0000000000000000000000000000000000000000..b41c8d2b06f8997968a6babeedfc49474f78f7f5
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_five.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_four.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_four.svg
new file mode 100644
index 0000000000000000000000000000000000000000..32d0e1ad276595c5513075198fa76cefc2ce7b09
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_four.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_minus.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_minus.svg
new file mode 100644
index 0000000000000000000000000000000000000000..08afe0f7bdf2e32bdde263c3e78e2a14c9d88d50
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_minus.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_multiply.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_multiply.svg
new file mode 100644
index 0000000000000000000000000000000000000000..3899760bc2aea51d0b0bfa3e1c203339e8436563
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_multiply.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_nine.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_nine.svg
new file mode 100644
index 0000000000000000000000000000000000000000..9b642e7cddfd06e2433f4042cc74d9c78da1cbce
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_nine.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_one.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_one.svg
new file mode 100644
index 0000000000000000000000000000000000000000..408b1944bd9b1609c2dbe75c2b030f288fc40107
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_one.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_percent.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_percent.svg
new file mode 100644
index 0000000000000000000000000000000000000000..745fc44f43cd336c31981940f496a3091eda23a9
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_percent.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_plus.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_plus.svg
new file mode 100644
index 0000000000000000000000000000000000000000..62e40d702a8342b46eb7c0864ef7009bf9138632
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_plus.svg
@@ -0,0 +1,15 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_point.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_point.svg
new file mode 100644
index 0000000000000000000000000000000000000000..62a5b528d3ede78e4687d047302abae0cfd8c783
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_point.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_seven.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_seven.svg
new file mode 100644
index 0000000000000000000000000000000000000000..cd41701f737a92a6df52eb3b8058dab7e3464bc4
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_seven.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_six.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_six.svg
new file mode 100644
index 0000000000000000000000000000000000000000..1831a2b9922c7162fa8f799e973a076731625b2b
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_six.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_three.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_three.svg
new file mode 100644
index 0000000000000000000000000000000000000000..26ab9bd51fbe9eb86e166cb7dca0ca2c7c3c2c67
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_three.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_two.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_two.svg
new file mode 100644
index 0000000000000000000000000000000000000000..314d1fe4974d0bd6c8dc5b8cfbcad1b75e09264e
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_two.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_zero.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_zero.svg
new file mode 100644
index 0000000000000000000000000000000000000000..7c2816caf1386519bbf2f31647931b066a0410b3
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_zero.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_zero_short.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_zero_short.svg
new file mode 100644
index 0000000000000000000000000000000000000000..5dc9ebe8e7ae9d0df8cc5e247f754da3f1d916aa
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_cal_zero_short.svg
@@ -0,0 +1,12 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_circle_open.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_circle_open.svg
new file mode 100644
index 0000000000000000000000000000000000000000..790e8a5b99436437f89dd3385da5eefb92b30103
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_circle_open.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_continue.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_continue.svg
new file mode 100644
index 0000000000000000000000000000000000000000..a5b250f868023c850e03bc6df0bea32596863f25
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_continue.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop.svg
new file mode 100644
index 0000000000000000000000000000000000000000..bc30a02a1d3ccffbad1b3670507e8c849a073395
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop.svg
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop_normal.png b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop_normal.png
new file mode 100644
index 0000000000000000000000000000000000000000..435ec58bbde7bc034fe558dc890af0b653ae2e2f
Binary files /dev/null and b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop_normal.png differ
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop_normal1.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop_normal1.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e104151dd4a2517dc6041d37e91901f13c8edd46
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_hop_normal1.svg
@@ -0,0 +1,18 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_pause.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_pause.svg
new file mode 100644
index 0000000000000000000000000000000000000000..b56bd247421f21b109ce95dd3ea2c7c8f464d522
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_pause.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_red_circle.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_red_circle.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e678e9735e410638dc03f10692ae5f817af51d80
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_red_circle.svg
@@ -0,0 +1,13 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_setting.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_setting.svg
new file mode 100644
index 0000000000000000000000000000000000000000..be1604576c904ca6d3ccee9a88c9267e786da6f1
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_setting.svg
@@ -0,0 +1,14 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_small_pause.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_small_pause.svg
new file mode 100644
index 0000000000000000000000000000000000000000..1508bfaacba8902244dc50997cf1e550d9b8a15f
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_small_pause.svg
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_stop.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_stop.svg
new file mode 100644
index 0000000000000000000000000000000000000000..cdbf8a235afe133b20e632ea953d859398af6c0d
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/ic_stop.svg
@@ -0,0 +1,7 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/icon.png b/test/hapAppDcameraSample/entry/src/main/resources/base/media/icon.png
new file mode 100644
index 0000000000000000000000000000000000000000..ce307a8827bd75456441ceb57d530e4c8d45d36c
Binary files /dev/null and b/test/hapAppDcameraSample/entry/src/main/resources/base/media/icon.png differ
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/layered_image.json b/test/hapAppDcameraSample/entry/src/main/resources/base/media/layered_image.json
new file mode 100644
index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/layered_image.json
@@ -0,0 +1,7 @@
+{
+ "layered-image":
+ {
+ "background" : "$media:background",
+ "foreground" : "$media:foreground"
+ }
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/startIcon.png b/test/hapAppDcameraSample/entry/src/main/resources/base/media/startIcon.png
new file mode 100644
index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b
Binary files /dev/null and b/test/hapAppDcameraSample/entry/src/main/resources/base/media/startIcon.png differ
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/take_video_normal.svg b/test/hapAppDcameraSample/entry/src/main/resources/base/media/take_video_normal.svg
new file mode 100644
index 0000000000000000000000000000000000000000..7904aedd2c5ceadac4e68487d6f75a886a3e5683
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/media/take_video_normal.svg
@@ -0,0 +1,10 @@
+
+
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/media/uncheck.png b/test/hapAppDcameraSample/entry/src/main/resources/base/media/uncheck.png
new file mode 100644
index 0000000000000000000000000000000000000000..cba71b7ec168e67e261151ee8cb9e950f53e7cbb
Binary files /dev/null and b/test/hapAppDcameraSample/entry/src/main/resources/base/media/uncheck.png differ
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/profile/backup_config.json b/test/hapAppDcameraSample/entry/src/main/resources/base/profile/backup_config.json
new file mode 100644
index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/profile/backup_config.json
@@ -0,0 +1,3 @@
+{
+ "allowToBackupRestore": true
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/base/profile/main_pages.json b/test/hapAppDcameraSample/entry/src/main/resources/base/profile/main_pages.json
new file mode 100644
index 0000000000000000000000000000000000000000..8f0137aa7e56367a33402a5b5ffcff2b9f2356b2
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/base/profile/main_pages.json
@@ -0,0 +1,7 @@
+{
+ "src": [
+ "pages/ListPage",
+ "recorder/AudioRecorder",
+ "recorder/VideoRecorder"
+ ]
+}
diff --git a/test/hapAppDcameraSample/entry/src/main/resources/dark/element/color.json b/test/hapAppDcameraSample/entry/src/main/resources/dark/element/color.json
new file mode 100644
index 0000000000000000000000000000000000000000..79b11c2747aec33e710fd3a7b2b3c94dd9965499
--- /dev/null
+++ b/test/hapAppDcameraSample/entry/src/main/resources/dark/element/color.json
@@ -0,0 +1,8 @@
+{
+ "color": [
+ {
+ "name": "start_window_background",
+ "value": "#000000"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/test/hapAppDcameraSample/hvigor/hvigor-config.json5 b/test/hapAppDcameraSample/hvigor/hvigor-config.json5
new file mode 100644
index 0000000000000000000000000000000000000000..5282eefe0593f56f8ddabfbf05783dc836c3c8b4
--- /dev/null
+++ b/test/hapAppDcameraSample/hvigor/hvigor-config.json5
@@ -0,0 +1,36 @@
+/*
+ * 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.
+ */
+{
+ "modelVersion": "5.0.1",
+ "dependencies": {
+ },
+ "execution": {
+ // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | false ]. Default: "normal" */
+ // "daemon": true, /* Enable daemon compilation. Value: [ true | false ]. Default: true */
+ // "incremental": true, /* Enable incremental compilation. Value: [ true | false ]. Default: true */
+ // "parallel": true, /* Enable parallel compilation. Value: [ true | false ]. Default: true */
+ // "typeCheck": false, /* Enable typeCheck. Value: [ true | false ]. Default: false */
+ },
+ "logging": {
+ // "level": "info" /* Define the log level. Value: [ "debug" | "info" | "warn" | "error" ]. Default: "info" */
+ },
+ "debugging": {
+ // "stacktrace": false /* Disable stacktrace compilation. Value: [ true | false ]. Default: false */
+ },
+ "nodeOptions": {
+ // "maxOldSpaceSize": 8192 /* Enable nodeOptions maxOldSpaceSize compilation. Unit M. Used for the daemon process. Default: 8192*/
+ // "exposeGC": true /* Enable to trigger garbage collection explicitly. Default: true*/
+ }
+}
diff --git a/test/hapAppDcameraSample/hvigorfile.ts b/test/hapAppDcameraSample/hvigorfile.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6b365cacd0191d3b1178eb6b9807b1ae0add6271
--- /dev/null
+++ b/test/hapAppDcameraSample/hvigorfile.ts
@@ -0,0 +1,20 @@
+/*
+ * 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 { appTasks } from '@ohos/hvigor-ohos-plugin';
+
+export default {
+ system: appTasks, /* Built-in plugin of Hvigor. It cannot be modified. */
+ plugins:[] /* Custom plugin to extend the functionality of Hvigor. */
+}
diff --git a/test/hapAppDcameraSample/oh-package-lock.json5 b/test/hapAppDcameraSample/oh-package-lock.json5
new file mode 100644
index 0000000000000000000000000000000000000000..a39bbd10ee88ce3ea78ab8c5f184ec085a6bb0e5
--- /dev/null
+++ b/test/hapAppDcameraSample/oh-package-lock.json5
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+{
+ "meta": {
+ "stableOrder": true
+ },
+ "lockfileVersion": 3,
+ "ATTENTION": "THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.",
+ "specifiers": {
+ "@ohos/hamock@1.0.0": "@ohos/hamock@1.0.0",
+ "@ohos/hypium@1.0.19": "@ohos/hypium@1.0.19"
+ },
+ "packages": {
+ "@ohos/hamock@1.0.0": {
+ "name": "@ohos/hamock",
+ "version": "1.0.0",
+ "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==",
+ "resolved": "https://repo.harmonyos.com/ohpm/@ohos/hamock/-/hamock-1.0.0.har",
+ "registryType": "ohpm"
+ },
+ "@ohos/hypium@1.0.19": {
+ "name": "@ohos/hypium",
+ "version": "1.0.19",
+ "integrity": "sha512-cEjDgLFCm3cWZDeRXk7agBUkPqjWxUo6AQeiu0gEkb3J8ESqlduQLSIXeo3cCsm8U/asL7iKjF85ZyOuufAGSQ==",
+ "resolved": "https://repo.harmonyos.com/ohpm/@ohos/hypium/-/hypium-1.0.19.har",
+ "registryType": "ohpm"
+ }
+ }
+}
diff --git a/test/hapAppDcameraSample/oh-package.json5 b/test/hapAppDcameraSample/oh-package.json5
new file mode 100644
index 0000000000000000000000000000000000000000..9b142d655b140175b33fe2a0c804ca498119f87f
--- /dev/null
+++ b/test/hapAppDcameraSample/oh-package.json5
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+{
+ "modelVersion": "5.0.1",
+ "description": "Please describe the basic information.",
+ "dependencies": {
+ },
+ "devDependencies": {
+ "@ohos/hypium": "1.0.19",
+ "@ohos/hamock": "1.0.0"
+ }
+}