diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/.gitignore b/MediaKit/AudioKit/AudioCaptureSampleJS/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/.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/MediaKit/AudioKit/AudioCaptureSampleJS/AppScope/app.json5 b/MediaKit/AudioKit/AudioCaptureSampleJS/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..02e14fba2e646d86166447488188b104168fecbc --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/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.myapplication", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:layered_image", + "label": "$string:app_name" + } +} diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/AppScope/resources/base/element/string.json b/MediaKit/AudioKit/AudioCaptureSampleJS/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..1080233f01384411ec684b58955cb8808746fdd3 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "MyApplication" + } + ] +} diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/AppScope/resources/base/media/background.png b/MediaKit/AudioKit/AudioCaptureSampleJS/AppScope/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/MediaKit/AudioKit/AudioCaptureSampleJS/AppScope/resources/base/media/background.png differ diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/AppScope/resources/base/media/foreground.png b/MediaKit/AudioKit/AudioCaptureSampleJS/AppScope/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9427585b36d14b12477435b6419d1f07b3e0bb Binary files /dev/null and b/MediaKit/AudioKit/AudioCaptureSampleJS/AppScope/resources/base/media/foreground.png differ diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/AppScope/resources/base/media/layered_image.json b/MediaKit/AudioKit/AudioCaptureSampleJS/AppScope/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/AppScope/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/MediaKit/AudioKit/AudioCaptureSampleJS/README.md b/MediaKit/AudioKit/AudioCaptureSampleJS/README.md new file mode 100644 index 0000000000000000000000000000000000000000..71503411dd156e84e60ddbcf92d208bd334aad44 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/README.md @@ -0,0 +1,112 @@ +# 实现音频录制功能 + +## 介绍 + +本示例基于AudioRender、AudioCapturer、AVPlayer以及CallServiceKit等能力,实现了音频录制、管理麦克风、音频录制流管理以及音频低时延耳返等功能,包含了功能调用接口的完整链路。 + +## 效果图预览 + +**图1**:首页 + +选择跳转到对应功能页面。 + + + +**图2**:音频录制页 + +- 依次点击'初始化'、'开始录制'按钮,即可开始录制音频。 +- 点击'停止录制'、'释放资源'按钮,即可结束录制。 +- 点击'查看状态'按钮,即可在日志信息下方打印当前录制流的状态。 + + + +**图3**:管理麦克风页 + +- 点击'创建音量组管理器'按钮,即可注册麦克风状态监听,事件触发后日志在回调信息下方打印。 +- 点击'查询麦克风是否静音'按钮,即可查询麦克风状态,查询结果在日志信息下方打印。 + + + +**图4**:音频录制流管理页 + +点击'获取当前录制流信息'按钮,即可进行录制流更改事件监听、获取当前录制流信息以及注销监听操作,其中获取到的录制流信息会在日志信息中打印。 + + + +**图5**:实现音频低时延耳返页 + +- 依次点击'创建音频低时延耳返实例'、'设置音频返听音量'、'设置混响模式'、'设置均衡器类型','启用音频返听'按钮,即可实现音频低时延耳返的配置。 +- 点击'禁用音频返听'按钮,即可实现低时延耳返的停用。 +- 点击查询相关按钮,返回信息会在日志信息下方打印。 + + + +## 工程结构&模块类型 + +``` +├───entry/src/main/ets +│ ├───pages +│ │ └───Index.ets // 首页。 +│ │ └───AudioCapture.ets // 使用AudioCapturer开发音频录制功能页。 +│ │ └───AudioLoopback.ets // 实现音频低时延耳返页。 +│ │ └───MacManager.ets // 音频录制流管理页。 +│ │ └───AudioStreamManager.ets // 管理麦克风页。 +└───entry/src/main/resources // 资源目录。 +``` + +## 具体实现 + +### 使用AudioCapturer开发音频录制功能 +- 源码参考:[AudioCapture.ets](entry/src/main/ets/pages/AudioCapture.ets) +- 使用流程: + - 需申请`ohos.permission.MICROPHONE`权限来获取麦克风权限保证录音可以正常起流。当获取权限成功后,后续再使用麦克风权限的接口时不需要重复申请该权限。 + - 点击'初始化'按钮,开始配置`AudioCapturerOptions`内容,包括采样率、通道数、采样格式、编码格式、音源类型以及采集器标志,接着配置读入数据回调并订阅监听,然后注册录制状态变化回调来监听流事件信息,最后调用`audio.createAudioCapturer`创建录制实例。 + - 点击'开始录制'按钮,调用`audioCapturer.start`,开始录制。 + - 点击'停止录制'按钮,调用`audioCapturer.stop`,停止录制。 + - 点击'释放资源'按钮,调用`audioCapturer.release`,释放音频流资源并注销回调。 + - 点击'获取状态'按钮,通过获取`audioCapturer.state`信息查看当前音频流状态。 + +### 管理麦克风状态 +- 源码参考:[MacManager.ets](entry/src/main/ets/pages/MacManager.ets) +- 使用流程: + - 点击'创建音量组管理器'按钮,调用`audio.getAudioManager().getVolumeManager().getVolumeGroupManager()`创建`AudioVolumeGroupManager`实例并设置监听,对麦克风静音状态变化进行响应。 + - 点击'查询麦克风是否静音'按钮,调用`audioVolumeGroupManager.isMicrophoneMute`查询当前麦克风是否静音。 + +### 音频录制流管理 +- 源码参考:[AudioStreamManager.ets](entry/src/main/ets/pages/AudioStreamManager.ets) +- 使用流程: + 点击'获取当前录制流信息'按钮,处理流程如下。 + - 通过`audioManager.getStreamManager`创建`audioStreamManager`实例,再通过`on('audioCapturerChange')`监听音频录制流变更时间相应流状态以及设备变化事件。 + - 通过调用`audioStreamManager.getCurrentAudioCapturerInfoArray`获取当前录制流信息,包括音频录制流的唯一ID、音频采集器信息以及音频录制设备信息。 + - 通过`off('audioCapturerChange')`注销监听音频录制流变化。 + +### 实现音频低时延耳返 +- 源码参考:[AudioLoopback.ets](entry/src/main/ets/pages/AudioLoopback.ets) +- 使用流程: + - 需要申请`ohos.permission.MICROPHONE`权限,如果前面使用过录音功能则这里无需再次申请麦克风权限。 + - 点击'创建音频低时延耳返实例'按钮,先调用`audio.getAudioManager().getStreamManager().isAudioLoopbackSupported(mode)`查询当前录制流是否支持低时延耳返,若支持低时延耳返功能,则接着调用`audio.createAudioLoopback`创建实例。 + - 点击'设置音频返听音量'按钮,调用`audioLoopback.setVolume`来设置音频返听的音量为0.5。音量取值范围为[0, 1]。 + - 点击'设置混响模式'按钮,调用`audioLoopback.setReverbPreset`来设置音频返听的混响模式为`audio.AudioLoopbackReverbPreset.THEATER`。 + - 点击'设置均衡器类型'按钮,调用`audioLoopback.setEqualizerPreset`来设置音频返听的均衡器类型为`audio.AudioLoopbackEqualizerPreset.FULL`。 + - 点击'查询返听状态'按钮,调用`audioLoopback.getStatus`来获取当前流音频返听的状态。 + - 点击'查询混响模式'按钮,调用`audioLoopback.getReverbPreset`来获取当前流音频返听的混响模式。 + - 点击'查询均衡器类型'按钮,调用`audioLoopback.getEqualizerPreset`来获取当前流音频返听的均衡器类型。 + - 点击'启用音频返听'按钮,调用`audioLoopback.enable`,配置参数为true,将当前流音频返听状态设置为启用。 + - 点击'禁用音频返听'按钮,调用`audioLoopback.enable`,配置参数为false,将当前流音频返听状态设置为禁用。 +## 相关权限 + +麦克风使用权限:ohos.permission.MICROPHONE + +## 模块依赖 + +不涉及。 + +## 约束与限制 + +1. 本示例支持在标准系统上运行,支持设备:RK3568。 + +2. 本示例支持API version 21,版本号: 6.0.1.112。 + +3. 本示例已支持使Build Version: 6.0.1.112, built on November 20, 2025。 + +4. 高等级APL特殊签名说明:无。 \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/build-profile.json5 b/MediaKit/AudioKit/AudioCaptureSampleJS/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..3b9a7bcc04b86b38badd941e5a1109732f2ce493 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/build-profile.json5 @@ -0,0 +1,56 @@ +/* +* 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": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "targetSdkVersion": "6.0.0(20)", + "compatibleSdkVersion": "6.0.0(20)", + "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/MediaKit/AudioKit/AudioCaptureSampleJS/code-linter.json5 b/MediaKit/AudioKit/AudioCaptureSampleJS/code-linter.json5 new file mode 100644 index 0000000000000000000000000000000000000000..62baae473144070aa0fc532fce773652507047a1 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/code-linter.json5 @@ -0,0 +1,46 @@ +/* +* 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": { + "@security/no-unsafe-aes": "error", + "@security/no-unsafe-hash": "error", + "@security/no-unsafe-mac": "warn", + "@security/no-unsafe-dh": "error", + "@security/no-unsafe-dsa": "error", + "@security/no-unsafe-ecdsa": "error", + "@security/no-unsafe-rsa-encrypt": "error", + "@security/no-unsafe-rsa-sign": "error", + "@security/no-unsafe-rsa-key": "error", + "@security/no-unsafe-dsa-key": "error", + "@security/no-unsafe-dh-key": "error", + "@security/no-unsafe-3des": "error" + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/.gitignore b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/build-profile.json5 b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..2ded9f2f9d44b4d00eb8dbe3fccae05dde77acb5 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/build-profile.json5 @@ -0,0 +1,47 @@ +/* +* 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": { + "resOptions": { + "copyCodeResource": { + "enable": false + } + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/hvigorfile.ts b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..61cca58be096424d17b6aadf3dff1c2ea06e05f3 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/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. */ +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/obfuscation-rules.txt b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/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/MediaKit/AudioKit/AudioCaptureSampleJS/entry/oh-package.json5 b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..cef04b2310ef09b8eaf16440e01c090501a79438 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/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": {} +} + diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/entryability/EntryAbility.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..4ae1807cdc1b76c6ef0123c184f575f74b21b8f6 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,62 @@ +/* +* 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 { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +const DOMAIN = 0x0000; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + try { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + } catch (err) { + hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err)); + } + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); + return; + } + hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..96164e538b152554e43e37a07d8ff4e7db326a0d --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,30 @@ +/* +* 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'; + +const DOMAIN = 0x0000; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(DOMAIN, 'testTag', 'onBackup ok'); + await Promise.resolve(); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + await Promise.resolve(); + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/AudioCapture.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/AudioCapture.ets new file mode 100644 index 0000000000000000000000000000000000000000..dbd5996d741b70a0dbe4695d02e214c42d747063 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/AudioCapture.ets @@ -0,0 +1,509 @@ +/* +* 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. +*/ +// [Start all_audioCapturer] +// [Start create_AudioCapturer] +import { audio } from '@kit.AudioKit'; +// [StartExclude create_AudioCapturer] +// [Start listen_AudioCapturer] +// [Start start_AudioCapturer] +// [Start stop_AudioCapturer] +// [Start release_AudioCapturer] +import { BusinessError } from '@kit.BasicServicesKit'; +// [StartExclude start_AudioCapturer] +// [StartExclude stop_AudioCapturer] +// [StartExclude release_AudioCapturer] +import { fileIo as fs } from '@kit.CoreFileKit'; +import { common, abilityAccessCtrl, PermissionRequestResult } from '@kit.AbilityKit'; +// [StartExclude listen_AudioCapturer] +const TAG = 'AudioCapturerDemo'; +// [EndExclude listen_AudioCapturer] +class Options { + offset?: number; + length?: number; +} +// [StartExclude listen_AudioCapturer] + +let audioCapturer: audio.AudioCapturer | undefined = undefined; +// [EndExclude create_AudioCapturer] +let audioStreamInfo: audio.AudioStreamInfo = { + samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率。 + channels: audio.AudioChannel.CHANNEL_2, // 通道。 + sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式。 + encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式。 +}; +let audioCapturerInfo: audio.AudioCapturerInfo = { + source: audio.SourceType.SOURCE_TYPE_MIC, // 音源类型:Mic音频源。根据业务场景配置,参考SourceType。 + capturerFlags: 0 // 音频采集器标志。 +}; +let audioCapturerOptions: audio.AudioCapturerOptions = { + streamInfo: audioStreamInfo, + capturerInfo: audioCapturerInfo +}; +// [StartExclude create_AudioCapturer] +let file: fs.File; +let readDataCallback: Callback; + +// [StartExclude all_audioCapturer] +async function requestMicrophonePermission(context: common.UIAbilityContext): Promise { + let atManager = abilityAccessCtrl.createAtManager(); + let result: PermissionRequestResult = await atManager + .requestPermissionsFromUser(context, ['ohos.permission.MICROPHONE']); + return result.authResults[0] === 0; +} +// [EndExclude all_audioCapturer] + +async function initArguments(context: common.UIAbilityContext): Promise { + // [EndExclude listen_AudioCapturer] + let bufferSize: number = 0; + let path = context.cacheDir; + let filePath = path + '/StarWars10s-2C-48000-4SW.pcm'; + file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); + readDataCallback = (buffer: ArrayBuffer) => { + let options: Options = { + offset: bufferSize, + length: buffer.byteLength + } + fs.writeSync(file.fd, buffer, options); + bufferSize += buffer.byteLength; + }; + // [StartExclude listen_AudioCapturer] +} + +// 初始化,创建实例,设置监听事件。 +async function init(updateCallback?: (msg: string, isError: boolean) => void, stateCallback?: + (msg: string) => void): Promise { + // [EndExclude create_AudioCapturer] + audio.createAudioCapturer(audioCapturerOptions, (err, capturer) => { // 创建AudioCapturer实例。 + if (err) { + console.error(`Invoke createAudioCapturer failed, code is ${err.code}, message is ${err.message}`); + // [StartExclude all_audioCapturer] + // [StartExclude create_AudioCapturer] + const errorMsg = `Invoke createAudioCapturer failed, code is ${err.code}, message is ${err.message}`; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude create_AudioCapturer] + // [EndExclude all_audioCapturer] + return; + } + console.info(`${TAG}: create AudioCapturer success`); + // [StartExclude all_audioCapturer] + // [StartExclude create_AudioCapturer] + const successMsg = `${TAG}: create AudioCapturer success`; + if (updateCallback) { + updateCallback(successMsg, false); + } + // [EndExclude create_AudioCapturer] + // [EndExclude all_audioCapturer] + audioCapturer = capturer; + if (audioCapturer !== undefined) { + // [EndExclude listen_AudioCapturer] + audioCapturer.on('readData', readDataCallback); + // [End listen_AudioCapturer] + // [StartExclude all_audioCapturer] + // [StartExclude create_AudioCapturer] + // 自动启动状态监听 + listenAudioCapturerState(stateCallback); + // [EndExclude create_AudioCapturer] + // [EndExclude all_audioCapturer] + } + }); + // [End create_AudioCapturer] +} + +// 开始一次音频采集。 +async function start(updateCallback?: (msg: string, isError: boolean) => void): Promise { + if (audioCapturer !== undefined) { + let stateGroup = [audio.AudioState.STATE_PREPARED + , audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED]; + // 当且仅当状态为STATE_PREPARED、STATE_PAUSED和STATE_STOPPED之一时才能启动采集。 + if (stateGroup.indexOf(audioCapturer.state.valueOf()) === -1) { + console.error(`${TAG}: start failed`); + // [StartExclude all_audioCapturer] + const errorMsg = `${TAG}: start failed`; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude all_audioCapturer] + return; + } + + // 启动采集。 + // [EndExclude start_AudioCapturer] + audioCapturer.start((err: BusinessError) => { + if (err) { + // [StartExclude all_audioCapturer] + // [StartExclude start_AudioCapturer] + const errorMsg = 'Capturer start failed.'; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude start_AudioCapturer] + // [EndExclude all_audioCapturer] + console.error('Capturer start failed.'); + } else { + // [StartExclude all_audioCapturer] + // [StartExclude start_AudioCapturer] + const successMsg = 'Capturer start success.'; + if (updateCallback) { + updateCallback(successMsg, false); + } + // [EndExclude start_AudioCapturer] + // [EndExclude all_audioCapturer] + console.info('Capturer start success.'); + } + }); + // [End start_AudioCapturer] + } +} + +// 停止采集。 +async function stop(updateCallback?: (msg: string, isError: boolean) => void): Promise { + if (audioCapturer !== undefined) { + // 只有采集器状态为STATE_RUNNING或STATE_PAUSED的时候才可以停止。 + if (audioCapturer.state.valueOf() !== audio.AudioState.STATE_RUNNING && audioCapturer.state.valueOf() + !== audio.AudioState.STATE_PAUSED) { + console.info('Capturer is not running or paused'); + // [StartExclude all_audioCapturer] + const infoMsg = 'Capturer is not running or paused'; + if (updateCallback) { + updateCallback(infoMsg, false); + } + // [EndExclude all_audioCapturer] + return; + } + + // 停止采集。 + // [EndExclude stop_AudioCapturer] + audioCapturer.stop((err: BusinessError) => { + if (err) { + // [StartExclude all_audioCapturer] + // [StartExclude stop_AudioCapturer] + const errorMsg = 'Capturer stop failed.'; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude stop_AudioCapturer] + // [EndExclude all_audioCapturer] + console.error('Capturer stop failed.'); + } else { + // [StartExclude all_audioCapturer] + // [StartExclude stop_AudioCapturer] + const successMsg = 'Capturer stop success.'; + if (updateCallback) { + updateCallback(successMsg, false); + } + // [EndExclude stop_AudioCapturer] + // [EndExclude all_audioCapturer] + console.info('Capturer stop success.'); + } + }); + // [End stop_AudioCapturer] + } +} + +// 销毁实例,释放资源。 +async function release(updateCallback?: (msg: string, isError: boolean) => void): Promise { + if (audioCapturer !== undefined) { + // 采集器状态不是STATE_RELEASED或STATE_NEW状态,才能release。 + if (audioCapturer.state.valueOf() === audio.AudioState.STATE_RELEASED + || audioCapturer.state.valueOf() === audio.AudioState.STATE_NEW) { + console.info('Capturer already released'); + // [StartExclude all_audioCapturer] + const infoMsg = 'Capturer already released'; + if (updateCallback) { + updateCallback(infoMsg, false); + } + // [EndExclude all_audioCapturer] + return; + } + + // 释放资源。 + // [EndExclude release_AudioCapturer] + audioCapturer.release((err: BusinessError) => { + if (err) { + // [StartExclude all_audioCapturer] + // [StartExclude release_AudioCapturer] + const errorMsg = 'Capturer release failed.'; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude release_AudioCapturer] + // [EndExclude all_audioCapturer] + console.error('Capturer release failed.'); + } else { + fs.closeSync(file); + console.info('Capturer release success.'); + // [StartExclude all_audioCapturer] + // [StartExclude release_AudioCapturer] + const successMsg = 'Capturer release success.'; + if (updateCallback) { + updateCallback(successMsg, false); + } + // [EndExclude release_AudioCapturer] + // [EndExclude all_audioCapturer] + } + }); + // [End release_AudioCapturer] + } +} + +// [StartExclude all_audioCapturer] +async function viewAudioCapturerState(updateCallback?: (msg: string, isError: boolean) => void): Promise { + if (audioCapturer !== undefined) { + // [Start view_AudioCapturerState] + let audioCapturerState: audio.AudioState = audioCapturer.state; + console.info(`Current state is: ${audioCapturerState }`) + // [End view_AudioCapturerState] + const stateMsg = `Current state is: ${audioCapturerState}`; + if (updateCallback) { + updateCallback(stateMsg, false); + } + } +} +// [EndExclude all_audioCapturer] + +// [StartExclude all_audioCapturer] +async function listenAudioCapturerState(callbackUpdate?: (msg: string) => void): Promise { + if (audioCapturer !== undefined) { + // [Start listen_AudioCapturerState] + audioCapturer.on('stateChange', (capturerState: audio.AudioState) => { + console.info(`State change to: ${capturerState}`) + // [StartExclude listen_AudioCapturerState] + const stateMsg = `State change to: ${capturerState}`; + if (callbackUpdate) { + callbackUpdate(stateMsg); + } + // [EndExclude listen_AudioCapturerState] + }); + // [End listen_AudioCapturerState] + } +} +// [EndExclude all_audioCapturer] +// [End all_audioCapturer] + +@Entry +@Component +struct Index { + @State currentState: string = '未初始化'; + @State logMessages: string = '暂无日志信息'; + @State callbackMessages: string = '暂无回调信息'; + + // 更新日志信息 + updateLogInfo(msg: string, isError: boolean): void { + const timestamp = new Date().toLocaleTimeString(); + const prefix = isError ? '[ERROR]' : '[INFO]'; + this.logMessages = `[${timestamp}] ${prefix} ${msg}`; + } + + // 更新回调信息 + updateCallbackInfo(msg: string): void { + const timestamp = new Date().toLocaleTimeString(); + this.callbackMessages = `[${timestamp}] ${msg}`; + } + + build(): void { + Scroll() { + Column() { + // 信息显示区域 + Column() { + Text('实时状态信息') + .fontSize(18) + .fontWeight(600) + .margin({ bottom: 12 }) + + // 当前状态 + Column() { + Text('当前状态') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.currentState) + .fontSize(14) + .fontColor('#007DFF') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 日志信息 + Column() { + Text('日志信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.logMessages) + .fontSize(13) + .fontColor('#52C41A') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + .maxLines(5) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 回调信息 + Column() { + Text('回调信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.callbackMessages) + .fontSize(13) + .fontColor('#FA8C16') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + .maxLines(3) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 20 }) + } + .width('100%') + .padding(16) + .backgroundColor(Color.White) + .borderRadius(12) + .margin({ bottom: 16 }) + + // 功能按钮 + Row() { + Column() { + Text('初始化').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 12, bottom: 12 }) + .onClick(async (): Promise => { + let context = this.getUIContext().getHostContext() as common.UIAbilityContext; + let hasPermission = await requestMicrophonePermission(context); + if (!hasPermission) { + console.error('麦克风权限未授权,无法录音'); + this.updateLogInfo('麦克风权限未授权,无法录音', true); + this.currentState = '权限未授权'; + return; + } + initArguments(context); + init((msg, isError) => this.updateLogInfo(msg, isError), (msg) => this.updateCallbackInfo(msg)); + this.currentState = '已初始化'; + }); + + Column() { + Text('开始录制').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + start((msg, isError) => this.updateLogInfo(msg, isError)); + this.currentState = '录制中'; + }); + } + + Row() { + Column() { + Text('停止录制').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 12, bottom: 12 }) + .onClick(async (): Promise => { + stop((msg, isError) => this.updateLogInfo(msg, isError)); + this.currentState = '已停止'; + }); + + Column() { + Text('释放资源').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + release((msg, isError) => this.updateLogInfo(msg, isError)); + this.currentState = '已释放'; + }); + } + + Row() { + Column() { + Text('查看状态').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 12, bottom: 12 }) + .onClick(async (): Promise => { + viewAudioCapturerState((msg, isError) => this.updateLogInfo(msg, isError)); + if (audioCapturer !== undefined) { + let state = audioCapturer.state; + let stateText = ''; + switch (state) { + case audio.AudioState.STATE_NEW: + stateText = '新建'; + break; + case audio.AudioState.STATE_PREPARED: + stateText = '准备就绪'; + break; + case audio.AudioState.STATE_RUNNING: + stateText = '运行中'; + break; + case audio.AudioState.STATE_STOPPED: + stateText = '已停止'; + break; + case audio.AudioState.STATE_RELEASED: + stateText = '已释放'; + break; + case audio.AudioState.STATE_PAUSED: + stateText = '已暂停'; + break; + default: + stateText = '未知'; + } + this.currentState = stateText; + } + }); + } + } + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5') + .padding(16); + } + } +} diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/AudioLoopback.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/AudioLoopback.ets new file mode 100644 index 0000000000000000000000000000000000000000..a23ea1d0fb7cfa4b659aebd69ae849c57f475c62 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/AudioLoopback.ets @@ -0,0 +1,664 @@ +/* +* 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. +*/ +// [Start all_audioLoopback] +// [Start create_AudioLoopback] +import { audio } from '@kit.AudioKit'; // 导入audio模块。 +// [Start get_Status] +// [Start set_Volume] +// [Start set_ReverbPreset] +// [Start get_ReverbPreset] +// [Start get_EqualizerPreset] +// [Start enable] +import { BusinessError } from '@kit.BasicServicesKit'; // 导入BusinessError。 +// [StartExclude create_AudioLoopback] +// [StartExclude get_Status] +// [StartExclude set_Volume] +// [StartExclude set_ReverbPreset] +// [StartExclude get_ReverbPreset] +// [StartExclude set_EqualizerPreset] +// [StartExclude get_EqualizerPreset] +// [StartExclude enable] +import { common, abilityAccessCtrl, PermissionRequestResult } from '@kit.AbilityKit'; // 导入UIAbilityContext。 + +const TAG = 'AudioLoopbackDemo'; +// [EndExclude create_AudioLoopback] +let mode: audio.AudioLoopbackMode = audio.AudioLoopbackMode.HARDWARE; +let audioLoopback: audio.AudioLoopback | undefined = undefined; +// [StartExclude create_AudioLoopback] +let currentReverbPreset: audio.AudioLoopbackReverbPreset = audio.AudioLoopbackReverbPreset.THEATER; +let currentEqualizerPreset: audio.AudioLoopbackEqualizerPreset = audio.AudioLoopbackEqualizerPreset.FULL; +// [StartExclude all_audioLoopback] +let statusChangeCallback = (status: audio.AudioLoopbackStatus) => { + if (status == audio.AudioLoopbackStatus.UNAVAILABLE_DEVICE) { + console.info('Audio loopback status is: UNAVAILABLE_DEVICE'); + } else if (status == audio.AudioLoopbackStatus.UNAVAILABLE_SCENE) { + console.info('Audio loopback status is: UNAVAILABLE_SCENE'); + } else if (status == audio.AudioLoopbackStatus.AVAILABLE_IDLE) { + console.info('Audio loopback status is: AVAILABLE_IDLE'); + } else if (status == audio.AudioLoopbackStatus.AVAILABLE_RUNNING) { + console.info('Audio loopback status is: AVAILABLE_RUNNING'); + } +}; +// [EndExclude all_audioLoopback] + +// [StartExclude all_audioLoopback] +async function requestMicrophonePermission(context: common.UIAbilityContext): Promise { + let atManager = abilityAccessCtrl.createAtManager(); + let result: PermissionRequestResult = await atManager + .requestPermissionsFromUser(context, ['ohos.permission.MICROPHONE']); + return result.authResults[0] === 0; +} +// [EndExclude all_audioLoopback] + +// 查询能力,创建实例。 +function init(updateCallback?: (msg: string, isError: boolean) => void): void { + // [EndExclude create_AudioLoopback] + let isSupported = audio.getAudioManager().getStreamManager().isAudioLoopbackSupported(mode); + if (isSupported) { + audio.createAudioLoopback(mode).then((loopback) => { + console.info('Invoke createAudioLoopback succeeded.'); + // [StartExclude all_audioLoopback] + // [StartExclude create_AudioLoopback] + const successMsg = 'Invoke createAudioLoopback succeeded.'; + if (updateCallback) { + updateCallback(successMsg, false); + } + // [EndExclude create_AudioLoopback] + // [EndExclude all_audioLoopback] + audioLoopback = loopback; + }).catch((err: BusinessError) => { + console.error(`Invoke createAudioLoopback failed, code is ${err.code}, message is ${err.message}.`); + // [StartExclude all_audioLoopback] + // [StartExclude create_AudioLoopback] + const errorMsg = `Invoke createAudioLoopback failed, code is ${err.code}, message is ${err.message}.`; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude create_AudioLoopback] + // [EndExclude all_audioLoopback] + }); + } else { + console.error('Audio loopback is unsupported.'); + // [StartExclude all_audioLoopback] + // [StartExclude create_AudioLoopback] + const errorMsg = 'Audio loopback is unsupported.'; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude create_AudioLoopback] + // [EndExclude all_audioLoopback] + } + // [End create_AudioLoopback] +} + +// 设置音频返听音量。 +async function setVolume(volume: number, updateCallback?: (msg: string, isError: boolean) => void): Promise { + if (audioLoopback !== undefined) { + // [EndExclude set_Volume] + try { + await audioLoopback.setVolume(volume); + console.info(`Invoke setVolume ${volume} succeeded.`); + // [StartExclude all_audioLoopback] + // [StartExclude set_Volume] + const successMsg = `Invoke setVolume ${volume} succeeded.`; + if (updateCallback) { + updateCallback(successMsg, false); + } + // [EndExclude set_Volume] + // [EndExclude all_audioLoopback] + } catch (err) { + console.error(`Invoke setVolume failed, code is ${err.code}, message is ${err.message}.`); + // [StartExclude all_audioLoopback] + // [StartExclude set_Volume] + const errorMsg = `Invoke setVolume failed, code is ${err.code}, message is ${err.message}.`; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude set_Volume] + // [EndExclude all_audioLoopback] + } + // [End set_Volume] + } else { + console.error('Audio loopback not created.'); + // [StartExclude all_audioLoopback] + const errorMsg = 'Audio loopback not created.'; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude all_audioLoopback] + } +} + +// 设置音频返听的混响模式。 +async function setReverbPreset(preset: audio.AudioLoopbackReverbPreset, updateCallback?: (msg: string, + isError: boolean) => void): Promise { + if (audioLoopback !== undefined) { + // [EndExclude set_ReverbPreset] + try { + audioLoopback.setReverbPreset(preset); + console.info(`setReverbPreset( ${preset} succeeded.`); + // [StartExclude all_audioLoopback] + // [StartExclude set_ReverbPreset] + const successMsg = `setReverbPreset ${preset} succeeded.`; + if (updateCallback) { + updateCallback(successMsg, false); + } + // [EndExclude set_ReverbPreset] + // [EndExclude all_audioLoopback] + currentReverbPreset = audioLoopback.getReverbPreset(); // 查询当前的混响模式,防止设置失败。 + } catch (err) { + console.error(`setReverbPreset( failed, code is ${err.code}, message is ${err.message}.`); + // [StartExclude all_audioLoopback] + // [StartExclude set_ReverbPreset] + const errorMsg = `setReverbPreset failed, code is ${err.code}, message is ${err.message}.`; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude set_ReverbPreset] + // [EndExclude all_audioLoopback] + } + // [End set_ReverbPreset] + } else { + console.error('Audio loopback not created.'); + // [StartExclude all_audioLoopback] + const errorMsg = 'Audio loopback not created.'; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude all_audioLoopback] + } +} + +// 设置音频返听的均衡器类型。 +async function setEqualizerPreset(preset: audio.AudioLoopbackEqualizerPreset, updateCallback?: + (msg: string, isError: boolean) => void): Promise { + if (audioLoopback !== undefined) { + // [EndExclude set_EqualizerPreset] + try { + audioLoopback.setEqualizerPreset(preset); + console.info(`setEqualizerPreset ${preset} succeeded.`); + // [StartExclude all_audioLoopback] + // [StartExclude set_EqualizerPreset] + const successMsg = `setEqualizerPreset ${preset} succeeded.`; + if (updateCallback) { + updateCallback(successMsg, false); + } + // [EndExclude set_EqualizerPreset] + // [EndExclude all_audioLoopback] + currentEqualizerPreset = audioLoopback.getEqualizerPreset(); // 查询当前的均衡器类型,防止设置失败。 + } catch (err) { + console.error(`setEqualizerPreset failed, code is ${err.code}, message is ${err.message}.`); + // [StartExclude all_audioLoopback] + // [StartExclude set_EqualizerPreset] + const errorMsg = `setEqualizerPreset failed, code is ${err.code}, message is ${err.message}.`; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude set_EqualizerPreset] + // [EndExclude all_audioLoopback] + } + // [End set_EqualizerPreset] + } else { + console.error('Audio loopback not created.'); + // [StartExclude all_audioLoopback] + const errorMsg = 'Audio loopback not created.'; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude all_audioLoopback] + } +} + +// [EndExclude enable] +// 设置监听事件,启用音频返听。 +async function enable(updateCallback?: (msg: string, isError: boolean) => void): Promise { + if (audioLoopback !== undefined) { + try { + let status = await audioLoopback.getStatus(); + if (status == audio.AudioLoopbackStatus.AVAILABLE_IDLE) { + // 注册监听。 + audioLoopback.on('statusChange', statusChangeCallback); + // 启动返听。 + let success = await audioLoopback.enable(true); + if (success) { + console.info('Invoke enable succeeded'); + // [StartExclude all_audioLoopback] + // [StartExclude enable] + const successMsg = `Invoke enable succeeded`; + if (updateCallback) { + updateCallback(successMsg, false); + } + // [EndExclude enable] + // [EndExclude all_audioLoopback] + } else { + status = await audioLoopback.getStatus(); + statusChangeCallback(status); + } + } else { + statusChangeCallback(status); + } + } catch (err) { + console.error(`Invoke enable failed, code is ${err.code}, message is ${err.message}.`); + // [StartExclude all_audioLoopback] + // [StartExclude enable] + const errorMsg = `Invoke enable failed, code is ${err.code}, message is ${err.message}.`; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude enable] + // [EndExclude all_audioLoopback] + } + } else { + console.error('Audio loopback not created.'); + // [StartExclude all_audioLoopback] + // [StartExclude enable] + const errorMsg = `Audio loopback not created.`; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude enable] + // [EndExclude all_audioLoopback] + } +} + +// 禁用音频返听,关闭监听事件。 +async function disable(updateCallback?: (msg: string, isError: boolean) => void): Promise { + if (audioLoopback !== undefined) { + try { + let status = await audioLoopback.getStatus(); + if (status == audio.AudioLoopbackStatus.AVAILABLE_RUNNING) { + // 禁用返听。 + let success = await audioLoopback.enable(false); + if (success) { + console.info('Invoke disable succeeded'); + // [StartExclude all_audioLoopback] + // [StartExclude enable] + const successMsg = `Invoke disable succeeded`; + if (updateCallback) { + updateCallback(successMsg, false); + } + // [EndExclude enable] + // [EndExclude all_audioLoopback] + // 关闭监听。 + audioLoopback.off('statusChange', statusChangeCallback); + } else { + status = await audioLoopback.getStatus(); + statusChangeCallback(status); + } + } else { + statusChangeCallback(status); + } + } catch (err) { + console.error(`Invoke disable failed, code is ${err.code}, message is ${err.message}.`); + // [StartExclude all_audioLoopback] + // [StartExclude enable] + const errorMsg = `Invoke disable failed, code is ${err.code}, message is ${err.message}.`; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude enable] + // [EndExclude all_audioLoopback] + } + } else { + console.error('Audio loopback not created.'); + // [StartExclude all_audioLoopback] + // [StartExclude enable] + const errorMsg = `Audio loopback not created.`; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude enable] + // [EndExclude all_audioLoopback] + } +} +// [End enable] +// [End all_audioLoopback] + +// [StartExclude all_audioLoopback] +// 查询当前返听状态 +async function getStatus(updateCallback?: (msg: string, isError: boolean) => void): Promise { + if (audioLoopback !== undefined) { + // [EndExclude get_Status] + audioLoopback.getStatus().then((status: audio.AudioLoopbackStatus) => { + console.info(`getStatus success, status is ${status}.`); + // [StartExclude get_Status] + const successMsg = `getStatus success, status is ${status}.`; + if (updateCallback) { + updateCallback(successMsg, false); + } + // [EndExclude get_Status] + }).catch((err: BusinessError) => { + console.error(`getStatus failed, code is ${err.code}, message is ${err.message}.`); + // [StartExclude get_Status] + const errorMsg = `getStatus failed, code is ${err.code}, message is ${err.message}.`; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude get_Status] + }) + // [End get_Status] + } +} + +// 查询当前的音频返听的混响模式 +async function getReverbPreset(updateCallback?: (msg: string, isError: boolean) => void): Promise { + if (audioLoopback !== undefined) { + // [EndExclude get_ReverbPreset] + try { + let reverbPreset = audioLoopback.getReverbPreset(); + } catch (err) { + console.error(`getReverbPreset:ERROR: ${err}`); + // [StartExclude get_ReverbPreset] + const errorMsg = `getReverbPreset:ERROR: ${err}`; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude get_ReverbPreset] + } + // [End get_ReverbPreset] + } +} + +// 查询当前的音频返听的均衡器类型 +async function getEqualizerPreset(updateCallback?: (msg: string, isError: boolean) => void): Promise { + if (audioLoopback !== undefined) { + // [EndExclude get_EqualizerPreset] + try { + let equalizerPreset = audioLoopback.getEqualizerPreset(); + } catch (err) { + console.error(`getEqualizerPreset:ERROR: ${err}`); + // [StartExclude get_EqualizerPreset] + const errorMsg = `getEqualizerPreset:ERROR: ${err}`; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude get_EqualizerPreset] + } + // [End get_EqualizerPreset] + } +} +// [EndExclude all_audioLoopback] + + +@Entry +@Component +struct Index { + @State currentState: string = '未初始化'; + @State logMessages: string = '暂无日志信息'; + @State callbackMessages: string = '暂无回调信息'; + + // 更新日志信息 + updateLogInfo(msg: string, isError: boolean): void { + const timestamp = new Date().toLocaleTimeString(); + const prefix = isError ? '[ERROR]' : '[INFO]'; + this.logMessages = `[${timestamp}] ${prefix} ${msg}`; + } + + // 更新回调信息 + updateCallbackInfo(msg: string): void { + const timestamp = new Date().toLocaleTimeString(); + this.callbackMessages = `[${timestamp}] ${msg}`; + } + + build(): void { + Scroll() { + Column() { + // 信息显示区域 + Column() { + Text('实时状态信息') + .fontSize(18) + .fontWeight(600) + .margin({ bottom: 12 }) + + // 当前状态 + Column() { + Text('当前状态') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.currentState) + .fontSize(14) + .fontColor('#007DFF') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 日志信息 + Column() { + Text('日志信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.logMessages) + .fontSize(13) + .fontColor('#52C41A') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + .maxLines(5) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 回调信息 + Column() { + Text('回调信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.callbackMessages) + .fontSize(13) + .fontColor('#FA8C16') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + .maxLines(3) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 20 }) + } + .width('100%') + .padding(16) + .backgroundColor(Color.White) + .borderRadius(12) + .margin({ bottom: 16 }) + + // 功能按钮 + Row() { + Column() { + Text('创建音频低时延耳返实例') + .fontColor(Color.Black) + .fontSize(14) + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .margin({ right: 12, bottom: 12 }) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .onClick(async (): Promise => { + let context = this.getUIContext().getHostContext() as common.UIAbilityContext; + // [StartExclude all_audioLoopback] + let hasPermission = await requestMicrophonePermission(context); + if (!hasPermission) { + const errorMsg = '麦克风权限未授权,无法录音'; + console.error(errorMsg); + this.updateLogInfo(errorMsg, true); + this.currentState = '权限未授权'; + return; + } + // [EndExclude all_audioLoopback] + init((msg, isError) => this.updateLogInfo(msg, isError)); + this.currentState = '已创建实例'; + }); + + Column() { + Text('设置音频返听音量') + .fontColor(Color.Black) + .fontSize(14) + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .margin({ bottom: 12 }) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .onClick(async (): Promise => { + await setVolume(0.5, (msg, isError) => this.updateLogInfo(msg, isError)); + }); + } + + Row() { + Column() { + Text('设置混响模式') + .fontColor(Color.Black) + .fontSize(14) + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .margin({ right: 12, bottom: 12 }) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .onClick(async (): Promise => { + await setReverbPreset(currentReverbPreset, (msg, isError) => this.updateLogInfo(msg, isError)); + }); + + Column() { + Text('设置均衡器类型') + .fontColor(Color.Black) + .fontSize(14) + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .margin({ bottom: 12 }) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .onClick(async (): Promise => { + await setEqualizerPreset(currentEqualizerPreset, (msg, isError) => this.updateLogInfo(msg, isError)); + }); + } + + Row() { + Column() { + Text('查询返听状态') + .fontColor(Color.Black) + .fontSize(14) + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .margin({ right: 12, bottom: 12 }) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .onClick(async (): Promise => { + await getStatus((msg, isError) => this.updateLogInfo(msg, isError)); + }); + + Column() { + Text('查询混响模式') + .fontColor(Color.Black) + .fontSize(14) + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .margin({ bottom: 12 }) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .onClick(async (): Promise => { + await getReverbPreset((msg, isError) => this.updateLogInfo(msg, isError)); + }); + } + + Row() { + Column() { + Text('查询均衡器类型') + .fontColor(Color.Black) + .fontSize(14) + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .margin({ right: 12, bottom: 12 }) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .onClick(async (): Promise => { + await getEqualizerPreset((msg, isError) => this.updateLogInfo(msg, isError)); + }); + + Column() { + Text('启用音频返听') + .fontColor(Color.Black) + .fontSize(14) + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .margin({ bottom: 12 }) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .onClick(async (): Promise => { + await enable((msg, isError) => this.updateLogInfo(msg, isError)); + this.currentState = '已启用返听'; + }); + } + + Row() { + Column() { + Text('禁用音频返听') + .fontColor(Color.Black) + .fontSize(14) + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .margin({ right: 12, bottom: 12 }) + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + .onClick(async (): Promise => { + await disable((msg, isError) => this.updateLogInfo(msg, isError)); + this.currentState = '已禁用返听'; + }); + } + } + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5') + .padding(16); + } + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/AudioStreamManager.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/AudioStreamManager.ets new file mode 100644 index 0000000000000000000000000000000000000000..f9abe97782de153f0b6ab33c3df4881fd306f19d --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/AudioStreamManager.ets @@ -0,0 +1,262 @@ +/* +* 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. +*/ +// [Start get_StreamManager] +import { audio } from '@kit.AudioKit'; +import { BusinessError } from '@kit.BasicServicesKit'; + +let audioManager = audio.getAudioManager(); +let audioStreamManager = audioManager.getStreamManager(); +// [End get_StreamManager] + +// 全局UI更新回调 +let globalCallbackUpdate: ((msg: string) => void) | undefined; + +function listenAudioStreamManager(): void { + // [Start audioStreamManager_on] + audioStreamManager.on('audioCapturerChange', (AudioCapturerChangeInfoArray: audio.AudioCapturerChangeInfoArray) => { + // [StartExclude audioStreamManager_on] + let infoMsg = ''; + // [EndExclude audioStreamManager_on] + for (let i = 0; i < AudioCapturerChangeInfoArray.length; i++) { + console.info(`## CapChange on is called for element ${i} ##`); + console.info(`StreamId for ${i} is: ${AudioCapturerChangeInfoArray[i].streamId}`); + console.info(`Source for ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.source}`); + console.info(`Flag ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.capturerFlags}`); + + // [StartExclude audioStreamManager_on] + // 为UI收集信息 + infoMsg += `CapChange on is called for element ${i}\n`; + infoMsg += `StreamId for ${i} is: ${AudioCapturerChangeInfoArray[i].streamId}\n`; + infoMsg += `Source for ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.source}\n`; + infoMsg += `Flag ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.capturerFlags}\n`; + // [EndExclude audioStreamManager_on] + + let devDescriptor: audio.AudioDeviceDescriptors = AudioCapturerChangeInfoArray[i].deviceDescriptors; + for (let j = 0; j < AudioCapturerChangeInfoArray[i].deviceDescriptors.length; j++) { + console.info(`Id: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].id}`); + console.info(`Type: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceType}`); + console.info(`Role: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceRole}`); + console.info(`Name: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].name}`); + console.info(`Address: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].address}`); + console.info(`SampleRates: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].sampleRates[0]}`); + console.info(`ChannelCounts ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelCounts[0]}`); + console.info(`ChannelMask: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelMasks}`); + } + } + // [StartExclude audioStreamManager_on] + // 更新UI + if (globalCallbackUpdate) { + globalCallbackUpdate(infoMsg); + } + // [EndExclude audioStreamManager_on] + }); + // [End audioStreamManager_on] +} + +function cancelListenAudioStreamManager(): void { + // [Start cancel_ListenAudioStreamManager] + audioStreamManager.off('audioCapturerChange'); + console.info('CapturerChange Off is called'); + // [End cancel_ListenAudioStreamManager] +} + +// [Start get_CurrentAudioCapturerInfoArray] +async function getCurrentAudioCapturerInfoArray(updateCallback?: + (msg: string, isError: boolean) => void): Promise{ + // [StartExclude get_CurrentAudioCapturerInfoArray] + // 获取前先监听 + listenAudioStreamManager(); + // [EndExclude get_CurrentAudioCapturerInfoArray] + + await audioStreamManager.getCurrentAudioCapturerInfoArray() + .then((AudioCapturerChangeInfoArray: audio.AudioCapturerChangeInfoArray) => { + console.info('getCurrentAudioCapturerInfoArray Get Promise Called'); + // [Start get_CurrentAudioCapturerInfoArray] + let detailInfo = 'getCurrentAudioCapturerInfoArray Get Promise Called\n'; + // [EndExclude get_CurrentAudioCapturerInfoArray] + if (AudioCapturerChangeInfoArray != null) { + for (let i = 0; i < AudioCapturerChangeInfoArray.length; i++) { + console.info(`StreamId for ${i} is: ${AudioCapturerChangeInfoArray[i].streamId}`); + console.info(`Source for ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.source}`); + console.info(`Flag ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.capturerFlags}`); + + detailInfo += `StreamId for ${i} is: ${AudioCapturerChangeInfoArray[i].streamId}\n`; + detailInfo += `Source for ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.source}\n`; + detailInfo += `Flag ${i} is: ${AudioCapturerChangeInfoArray[i].capturerInfo.capturerFlags}\n`; + + for (let j = 0; j < AudioCapturerChangeInfoArray[i].deviceDescriptors.length; j++) { + console.info(`Id: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].id}`); + console.info(`Type: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceType}`); + console.info(`Role: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].deviceRole}`); + console.info(`Name: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].name}`); + console.info(`Address: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].address}`); + console.info(`SampleRates: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].sampleRates[0]}`); + console.info(`ChannelCounts ${i} : ${AudioCapturerChangeInfoArray[i] + .deviceDescriptors[j].channelCounts[0]}`); + console.info(`ChannelMask: ${i} : ${AudioCapturerChangeInfoArray[i].deviceDescriptors[j].channelMasks}`); + } + } + } + // [Start get_CurrentAudioCapturerInfoArray] + if (updateCallback) { + updateCallback(detailInfo, false); + } + // [EndExclude get_CurrentAudioCapturerInfoArray] + }).catch((err: BusinessError) => { + console.error(`Invoke getCurrentAudioCapturerInfoArray failed, code is ${err.code}, message is ${err.message}`); + // [Start get_CurrentAudioCapturerInfoArray] + const errorMsg = `Invoke getCurrentAudioCapturerInfoArray failed, code is ${err.code}, message is ${err.message}`; + if (updateCallback) { + updateCallback(errorMsg, true); + } + // [EndExclude get_CurrentAudioCapturerInfoArray] + }); + // [Start get_CurrentAudioCapturerInfoArray] + // 获取后取消监听 + cancelListenAudioStreamManager(); + // [EndExclude get_CurrentAudioCapturerInfoArray] +} +// [End get_CurrentAudioCapturerInfoArray] + +@Entry +@Component +struct Index { + @State currentState: string = '未初始化'; + @State logMessages: string = '暂无日志信息'; + @State callbackMessages: string = '暂无回调信息'; + + aboutToAppear(): void { + // 设置全局回调 + globalCallbackUpdate = (msg) => this.updateCallbackInfo(msg); + } + + // 更新日志信息 + updateLogInfo(msg: string, isError: boolean): void { + const timestamp = new Date().toLocaleTimeString(); + const prefix = isError ? '[ERROR]' : '[INFO]'; + this.logMessages = `[${timestamp}] ${prefix} ${msg}`; + } + + // 更新回调信息 + updateCallbackInfo(msg: string): void { + const timestamp = new Date().toLocaleTimeString(); + this.callbackMessages = `[${timestamp}] ${msg}`; + } + + build(): void { + Scroll() { + Column() { + // 信息显示区域 + Column() { + Text('实时状态信息') + .fontSize(18) + .fontWeight(600) + .margin({ bottom: 12 }) + + // 当前状态 + Column() { + Text('当前状态') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.currentState) + .fontSize(14) + .fontColor('#007DFF') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 日志信息 + Column() { + Text('日志信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.logMessages) + .fontSize(12) + .fontColor('#52C41A') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 回调信息 + Column() { + Text('回调信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.callbackMessages) + .fontSize(12) + .fontColor('#FA8C16') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 20 }) + } + .width('100%') + .padding(16) + .backgroundColor(Color.White) + .borderRadius(12) + .margin({ bottom: 16 }) + + // 功能按钮 + Column() { + Text('获取当前录制流信息').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('90%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在获取录制流信息...'; + await getCurrentAudioCapturerInfoArray((msg, isError) => { + this.updateLogInfo(msg, isError); + if (!isError) { + this.currentState = '获取成功'; + } else { + this.currentState = '获取失败'; + } + }); + }); + } + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5') + .padding(16); + } + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/Index.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..e1029705624ba96fcd7e59ab9fd192425dee1a33 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,55 @@ +/* +* 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 {Router} from '@ohos.arkui.UIContext'; +const router = new Router(); + +@Entry +@Component +struct Index { + build() { + Column() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center}) { + this.textComponent('Index'); + this.buttonComponent('跳转到开发音频录制功能页面', 'pages/AudioCapture'); + this.buttonComponent('跳转到管理麦克风页面', 'pages/MacManager'); + this.buttonComponent('跳转到音频录制流管理页面', 'pages/AudioStreamManager'); + this.buttonComponent('跳转到实现低时延耳返页面', 'pages/AudioLoopback'); + }.size({ width: '100%', height: '100%' }) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + } + + @Builder + textComponent(name: string) { + Text(name) + .textAlign(TextAlign.Center) + .margin(10) + .fontSize($r('app.float.index_text_font_size')) + .size({ width: '100%', height: $r('app.float.index_button_height_size') }) + } + + @Builder + buttonComponent(name: string, dstUri: string) { + Button(name) + .width('80%') + .margin(10) + .onClick((e: ClickEvent) => { + router.pushUrl({ url: dstUri }); + }) + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/MacManager.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/MacManager.ets new file mode 100644 index 0000000000000000000000000000000000000000..df8cf12b9dcceafb5edb146a3f2527d317abdd27 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/ets/pages/MacManager.ets @@ -0,0 +1,188 @@ +/* +* 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. +*/ +// [Start create_AudioVolumeGroupManager] +import { audio } from '@kit.AudioKit'; + +let audioVolumeGroupManager: audio.AudioVolumeGroupManager; +// 创建audioVolumeGroupManager对象。 +async function loadVolumeGroupManager(updateCallback?: (msg: string, isError: boolean) => void): Promise { + const groupid = audio.DEFAULT_VOLUME_GROUP_ID; + audioVolumeGroupManager = await audio.getAudioManager().getVolumeManager().getVolumeGroupManager(groupid); + console.info('audioVolumeGroupManager create success.'); + // [StartExclude create_AudioVolumeGroupManager] + on(); + const successMsg = 'audioVolumeGroupManager create success.'; + if (updateCallback) { + updateCallback(successMsg, false); + } + // [EndExclude create_AudioVolumeGroupManager] +} +// [End create_AudioVolumeGroupManager] + +// [Start mac_on] +// 监听麦克风状态变化。 +async function on() { + audioVolumeGroupManager.on('micStateChange', (micStateChange: audio.MicStateChangeEvent) => { + console.info(`Current microphone status is: ${micStateChange.mute} `); + }); +} +// [End mac_on] + +// [Start is_MicrophoneMute] +// 查询麦克风是否静音。 +async function isMicrophoneMute(updateCallback?: (msg: string, isError: boolean) => void): Promise { + await audioVolumeGroupManager.isMicrophoneMute().then((value: boolean) => { + console.info(`isMicrophoneMute is: ${value}.`); + // [StartExclude is_MicrophoneMute] + const infoMsg = `isMicrophoneMute is: ${value}.`; + if (updateCallback) { + updateCallback(infoMsg, false); + } + // [EndExclude is_MicrophoneMute] + }); +} +// [End is_MicrophoneMute] + +@Entry +@Component +struct Index { + @State currentState: string = '未初始化'; + @State logMessages: string = '暂无日志信息'; + @State callbackMessages: string = '暂无回调信息'; + + // 更新日志信息 + updateLogInfo(msg: string, isError: boolean): void { + const timestamp = new Date().toLocaleTimeString(); + const prefix = isError ? '[ERROR]' : '[INFO]'; + this.logMessages = `[${timestamp}] ${prefix} ${msg}`; + } + + // 更新回调信息 + updateCallbackInfo(msg: string): void { + const timestamp = new Date().toLocaleTimeString(); + this.callbackMessages = `[${timestamp}] ${msg}`; + } + + build(): void { + Scroll() { + Column() { + // 信息显示区域 + Column() { + Text('实时状态信息') + .fontSize(18) + .fontWeight(600) + .margin({ bottom: 12 }) + + // 当前状态 + Column() { + Text('当前状态') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.currentState) + .fontSize(14) + .fontColor('#007DFF') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 日志信息 + Column() { + Text('日志信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.logMessages) + .fontSize(13) + .fontColor('#52C41A') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + .maxLines(5) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 回调信息 + Column() { + Text('回调信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.callbackMessages) + .fontSize(13) + .fontColor('#FA8C16') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + .maxLines(3) + .textOverflow({ overflow: TextOverflow.Ellipsis }) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 20 }) + } + .width('100%') + .padding(16) + .backgroundColor(Color.White) + .borderRadius(12) + .margin({ bottom: 16 }) + + // 功能按钮 + Row() { + Column() { + Text('创建音量组管理器').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 12, bottom: 12 }) + .onClick(async (): Promise => { + await loadVolumeGroupManager((msg, isError) => this.updateLogInfo(msg, isError)); + this.currentState = '已创建音量组管理器'; + }); + + Column() { + Text('查询麦克风是否静音').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + await isMicrophoneMute((msg, isError) => this.updateLogInfo(msg, isError)); + }); + } + } + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5') + .padding(16); + } + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/module.json5 b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..5cd81baaff5c7c86a6ed4aeea60b10b90d859058 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/module.json5 @@ -0,0 +1,100 @@ +/* +* 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": [ + "default" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "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": [ + "ohos.want.action.home" + ] + }, + { + "actions": [ + // actions不能为空,actions为空会造成目标方匹配失败。 + "ohos.want.action.viewData" + ], + "uris": [ + { + // scheme必选,可以自定义,以link为例,需要替换为实际的scheme + "scheme": "audioTest", + // host必选,配置待匹配的域名 + "host": "www.audioTest.com" + } + ] + } // 新增一个skill对象,用于跳转场景。如果存在多个跳转场景,需配置多个skill对象。 + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ] + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.MICROPHONE", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "always" + } + }, + { + "name": "ohos.permission.ACCESS_NOTIFICATION_POLICY", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "always" + } + }, + ] + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/element/color.json b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/element/float.json b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..801bc27a39ab78e230cb049d46c0b4f7acb5e01d --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/element/float.json @@ -0,0 +1,16 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + }, + { + "name": "index_text_font_size", + "value": "50fp" + }, + { + "name": "index_button_height_size", + "value": "80vp" + } + ] +} diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/element/string.json b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f94595515a99e0c828807e243494f57f09251930 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/media/background.png b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/media/background.png differ diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/media/foreground.png b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97014d3e10e5ff511409c378cd4255713aecd85f Binary files /dev/null and b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/media/foreground.png differ diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/media/layered_image.json b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/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/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/media/startIcon.png b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/media/startIcon.png differ diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/profile/backup_config.json b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/profile/main_pages.json b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..906f4cceb8b7f1979877c9658eb2df2ebfe56697 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,9 @@ +{ + "src": [ + "pages/Index", + "pages/AudioCapture", + "pages/MacManager", + "pages/AudioStreamManager", + "pages/AudioLoopback" + ] +} diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/dark/element/color.json b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/main/resources/dark/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..79b11c2747aec33e710fd3a7b2b3c94dd9965499 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/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/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..b3c08584b8d262fa6c3efee04a7d81e3c973f53d --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,49 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/AudioCapturerTest.test.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/AudioCapturerTest.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..1e99f4187e61d32374a9245999081227aa59e8ca --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/AudioCapturerTest.test.ets @@ -0,0 +1,182 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, Level } from '@ohos/hypium'; +import { Driver, ON, MatchPattern } from '@ohos.UiTest'; +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const TAG = '[Sample_AudioCapturer]'; +const DOMAIN = 0xF811; +const BUNDLE = 'AudioCapturer_'; +let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const driver = Driver.create(); + +async function runTest(testCaseName: string, buttonText: string, delayTime: number, description: string, + done: Function) { + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, begin`); + + let btnAccept = await driver.findComponent(ON.text(buttonText)); + if (btnAccept !== undefined) { + await btnAccept.click(); + await driver.delayMs(delayTime); + } + else { + await driver.delayMs(delayTime); + } + + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, end`); + done(); +} + +export default function AudioCapturerTest() { + describe('AudioCapturerTest', () => { + beforeAll(() => { + abilityDelegator.startAbility({ + bundleName: 'com.example.myapplication', + abilityName: 'EntryAbility' + }); + }) + beforeEach(() => {}) + afterEach(() => {}) + afterAll(() => {}) + + /** + * @tc.number AudioCapturer_EnterPage_0001 + * @tc.name AudioCapturer_EnterPage_0001 + * @tc.desc Enter audio capture page + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioCapturer_EnterPage_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioCapturer_EnterPage_0001', + '跳转到开发音频录制功能页面', + 8000, + 'Enter_AudioCapture_Page', + done + ); + }) + + /** + * @tc.number AudioCapturer_Init_0001 + * @tc.name AudioCapturer_Init_0001 + * @tc.desc Initialize audio capturer + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioCapturer_Init_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioCapturer_Init_0001', + '初始化', + 3000, + 'AudioCapturer_Init', + done + ); + }) + + /** + * @tc.number AudioCapturer_GetAccess_0001 + * @tc.name AudioCapturer_GetAccess_0001 + * @tc.desc Request microphone permission + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioCapturer_GetAccess_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioCapturer_GetAccess_0001', + '允许', + 5000, + 'GetAccess_0001', + done + ); + }) + + /** + * @tc.number AudioCapturer_Start_0001 + * @tc.name AudioCapturer_Start_0001 + * @tc.desc Start audio recording + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioCapturer_Start_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioCapturer_Start_0001', + '开始录制', + 3000, + 'AudioCapturer_Start', + done + ); + }) + + /** + * @tc.number AudioCapturer_ViewState_0001 + * @tc.name AudioCapturer_ViewState_0001 + * @tc.desc View audio capturer state + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioCapturer_ViewState_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioCapturer_ViewState_0001', + '查看状态', + 2000, + 'AudioCapturer_ViewState', + done + ); + }) + + /** + * @tc.number AudioCapturer_Stop_0001 + * @tc.name AudioCapturer_Stop_0001 + * @tc.desc Stop audio recording + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioCapturer_Stop_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioCapturer_Stop_0001', + '停止录制', + 2000, + 'AudioCapturer_Stop', + done + ); + }) + + /** + * @tc.number AudioCapturer_Release_0001 + * @tc.name AudioCapturer_Release_0001 + * @tc.desc Release audio capturer resources + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioCapturer_Release_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioCapturer_Release_0001', + '释放资源', + 2000, + 'AudioCapturer_Release', + done + ); + }) + }) +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/AudioLoopbackTest.test.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/AudioLoopbackTest.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..8b1280708b957fa9b8b9fc8b8d6711e12798976d --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/AudioLoopbackTest.test.ets @@ -0,0 +1,254 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, Level } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const TAG = '[Sample_AudioLoopback]'; +const DOMAIN = 0xF812; +const BUNDLE = 'AudioLoopback_'; +let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const driver = Driver.create(); + +async function runTest(testCaseName: string, buttonText: string, delayTime: number, description: string, + done: Function) { + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, begin`); + + let btn = await driver.findComponent(ON.text(buttonText)); + if (btn !== undefined) { + await btn.click(); + await driver.delayMs(delayTime); + } else { + await driver.delayMs(delayTime); + } + + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, end`); + done(); +} + +export default function AudioLoopbackTest() { + describe('AudioLoopbackTest', () => { + beforeAll(() => { + abilityDelegator.startAbility({ + bundleName: 'com.example.myapplication', + abilityName: 'EntryAbility' + }); + }); + + beforeEach(() => {}) + afterEach(() => {}) + afterAll(() => {}) + + /** + * @tc.number AudioCapturer_NavigateToLoopbackPage_0001 + * @tc.name AudioCapturer_NavigateToLoopbackPage_0001 + * @tc.desc Navigate to low-latency audio loopback implementation page + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioCapturer_NavigateToLoopbackPage_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioCapturer_NavigateToLoopbackPage_0001', + '跳转到实现低时延耳返页面', + 5000, + 'Navigate_To_LowLatency_Loopback_Page', + done + ); + }) + + /** + * @tc.number AudioLoopback_CreateInstance_0001 + * @tc.name AudioLoopback_CreateInstance_0001 + * @tc.desc Create audio loopback instance + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioLoopback_CreateInstance_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioLoopback_CreateInstance_0001', + '创建音频低时延耳返实例', + 5000, + 'Create_AudioLoopback_Instance', + done + ); + }) + + /** + * @tc.number AudioLoopback_GrantPermission_0001 + * @tc.name AudioLoopback_GrantPermission_0001 + * @tc.desc Request microphone permission + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioLoopback_GrantPermission_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioLoopback_GrantPermission_0001', + '允许', + 3000, + 'Grant_Microphone_Permission', + done + ); + }) + + /** + * @tc.number AudioLoopback_SetVolume_0001 + * @tc.name AudioLoopback_SetVolume_0001 + * @tc.desc Set audio loopback volume + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioLoopback_SetVolume_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioLoopback_SetVolume_0001', + '设置音频返听音量', + 2000, + 'Set_AudioLoopback_Volume', + done + ); + }) + + /** + * @tc.number AudioLoopback_SetReverb_0001 + * @tc.name AudioLoopback_SetReverb_0001 + * @tc.desc Set audio loopback reverb preset + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioLoopback_SetReverb_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioLoopback_SetReverb_0001', + '设置混响模式', + 2000, + 'Set_AudioLoopback_Reverb', + done + ); + }) + + /** + * @tc.number AudioLoopback_SetEqualizer_0001 + * @tc.name AudioLoopback_SetEqualizer_0001 + * @tc.desc Set audio loopback equalizer preset + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioLoopback_SetEqualizer_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioLoopback_SetEqualizer_0001', + '设置均衡器类型', + 2000, + 'Set_AudioLoopback_Equalizer', + done + ); + }) + + /** + * @tc.number AudioLoopback_GetStatus_0001 + * @tc.name AudioLoopback_GetStatus_0001 + * @tc.desc Get audio loopback status + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioLoopback_GetStatus_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioLoopback_GetStatus_0001', + '查询返听状态', + 2000, + 'Get_AudioLoopback_Status', + done + ); + }) + + /** + * @tc.number AudioLoopback_GetReverb_0001 + * @tc.name AudioLoopback_GetReverb_0001 + * @tc.desc Get current reverb preset + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioLoopback_GetReverb_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioLoopback_GetReverb_0001', + '查询混响模式', + 2000, + 'Get_AudioLoopback_Reverb', + done + ); + }) + + /** + * @tc.number AudioLoopback_GetEqualizer_0001 + * @tc.name AudioLoopback_GetEqualizer_0001 + * @tc.desc Get current equalizer preset + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioLoopback_GetEqualizer_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioLoopback_GetEqualizer_0001', + '查询均衡器类型', + 2000, + 'Get_AudioLoopback_Equalizer', + done + ); + }) + + /** + * @tc.number AudioLoopback_Enable_0001 + * @tc.name AudioLoopback_Enable_0001 + * @tc.desc Enable audio loopback + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioLoopback_Enable_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioLoopback_Enable_0001', + '启用音频返听', + 3000, + 'Enable_AudioLoopback', + done + ); + }) + + /** + * @tc.number AudioLoopback_Disable_0001 + * @tc.name AudioLoopback_Disable_0001 + * @tc.desc Disable audio loopback + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioLoopback_Disable_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioLoopback_Disable_0001', + '禁用音频返听', + 3000, + 'Disable_AudioLoopback', + done + ); + }) + }) +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/AudioStreamManager.test.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/AudioStreamManager.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..b8a27ba5066ac8b17cb2f140906472413bd74d81 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/AudioStreamManager.test.ets @@ -0,0 +1,92 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, Level } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const TAG = '[AudioCapturerInfo]'; +const DOMAIN = 0xF813; +const BUNDLE = 'AudioCapturerInfo_'; +let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const driver = Driver.create(); + +async function runTest(testCaseName: string, buttonText: string, delayTime: number, description: string, + done: Function) { + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, begin`); + + let btn = await driver.findComponent(ON.text(buttonText)); + if (btn !== undefined) { + await btn.click(); + await driver.delayMs(delayTime); + } else { + await driver.delayMs(delayTime); + } + + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, end`); + done(); +} + +export default function AudioStreamManagerTest() { + describe('AudioStreamManagerTest', () => { + beforeAll(() => { + abilityDelegator.startAbility({ + bundleName: 'com.example.myapplication', + abilityName: 'EntryAbility' + }); + }); + + beforeEach(() => {}) + afterEach(() => {}) + afterAll(() => {}) + + /** + * @tc.number AudioCapturerInfo_EnterPage_0001 + * @tc.name AudioCapturerInfo_EnterPage_0001 + * @tc.desc Enter and jump to the audio recording stream management page + * @tc.size SmallTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioCapturerInfo_EnterPage_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioCapturerInfo_EnterPage_0001', + '跳转到音频录制流管理页面', + 5000, + 'Verify that the audio capturer info page loads correctly', + done + ); + }) + + /** + * @tc.number AudioCapturerInfo_GetCurrentInfo_0001 + * @tc.name AudioCapturerInfo_GetCurrentInfo_0001 + * @tc.desc Retrieve current recording stream information + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioCapturerInfo_GetCurrentInfo_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioCapturerInfo_GetCurrentInfo_0001', + '获取当前录制流信息', + 6000, + 'Click get current audio capturer info button', + done + ); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/List.test.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..5b9f200edf44ec77833410d25d3a43e29a6c18f9 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,13 @@ +import abilityTest from './Ability.test'; +import AudioCapturerTest from './AudioCapturerTest.test'; +import AudioLoopbackDemoTest from './AudioLoopbackTest.test'; +import AudioStreamManagerTest from './AudioStreamManager.test'; +import AudioVolumeGroupManagerTest from './MacManager.test'; + +export default function testsuite() { + abilityTest(); + AudioCapturerTest(); + AudioLoopbackDemoTest(); + AudioVolumeGroupManagerTest(); + AudioStreamManagerTest(); +} diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/macManager.test.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/macManager.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..082bef809543f58b94524ec33091d574e53c7a51 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/ets/test/macManager.test.ets @@ -0,0 +1,110 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, Level } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const TAG = '[AudioMicMute]'; +const DOMAIN = 0xF814; +const BUNDLE = 'AudioMicMute_'; +let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const driver = Driver.create(); + +async function runTest(testCaseName: string, buttonText: string, delayTime: number, description: string, + done: Function) { + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, begin`); + + let btn = await driver.findComponent(ON.text(buttonText)); + if (btn !== undefined) { + await btn.click(); + await driver.delayMs(delayTime); + } else { + await driver.delayMs(delayTime); + } + + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, end`); + done(); +} + +export default function AudioMicMuteTest() { + describe('AudioMicMuteTest', () => { + beforeAll(() => { + abilityDelegator.startAbility({ + bundleName: 'com.example.myapplication', + abilityName: 'EntryAbility' + }); + }); + + beforeEach(() => {}) + afterEach(() => {}) + afterAll(() => {}) + + /** + * @tc.number AudioMicMute_EnterPage_0001 + * @tc.name AudioMicMute_EnterPage_0001 + * @tc.desc Enter and jump to the microphone management page + * @tc.size SmallTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioMicMute_EnterPage_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioMicMute_EnterPage_0001', + '跳转到管理麦克风页面', + 5000, + 'Verify that the microphone mute control page loads correctly', + done + ); + }) + + /** + * @tc.number AudioMicMute_CreateManager_0001 + * @tc.name AudioMicMute_CreateManager_0001 + * @tc.desc Create a volume group manager + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioMicMute_CreateManager_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioMicMute_CreateManager_0001', + '创建音量组管理器', + 3000, + 'Create audio volume group manager', + done + ); + }); + + /** + * @tc.number AudioMicMute_QueryMuteStatus_0001 + * @tc.name AudioMicMute_QueryMuteStatus_0001 + * @tc.desc Check if the microphone is muted + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioMicMute_QueryMuteStatus_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioMicMute_QueryMuteStatus_0001', + '查询麦克风是否静音', + 3000, + 'Query microphone mute status', + done + ); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/module.json5 b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..0a099ee4bd05a001f943915cd4eb2e01aabeaca0 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/ohosTest/module.json5 @@ -0,0 +1,25 @@ +/* +* 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_test", + "type": "feature", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/test/List.test.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..c2359615fdfb661f743a2cac5886892209c7aee6 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/test/List.test.ets @@ -0,0 +1,19 @@ +/* +* 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 localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/test/LocalUnit.test.ets b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..45ca114e698503447e7ccc80364c54cf7fa50f46 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,47 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/hvigor/hvigor-config.json5 b/MediaKit/AudioKit/AudioCaptureSampleJS/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7d7d5f8aa0d0c54d1c8daf78625b9b3bbf52aee8 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/hvigor/hvigor-config.json5 @@ -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. +*/ +{ + "modelVersion": "6.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | 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 */ + // "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */ + }, + "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/MediaKit/AudioKit/AudioCaptureSampleJS/hvigorfile.ts b/MediaKit/AudioKit/AudioCaptureSampleJS/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c5b9939f0c562551a58ebd26abae33caf4f5e28 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/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. */ +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/oh-package.json5 b/MediaKit/AudioKit/AudioCaptureSampleJS/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..d4c67d9bed2040005240f4f66a6601227e30809b --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/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": "6.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.24", + "@ohos/hamock": "1.0.0" + } +} diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/ohosTest.md b/MediaKit/AudioKit/AudioCaptureSampleJS/ohosTest.md new file mode 100644 index 0000000000000000000000000000000000000000..438d33139d6778547151221cd4c2a0ea5d5a1168 --- /dev/null +++ b/MediaKit/AudioKit/AudioCaptureSampleJS/ohosTest.md @@ -0,0 +1,29 @@ +# 音频录制测试用例归档 + +## 用例表 + +| 测试功能 | 预置条件 | 输入 | 预期输出 | 是否自动 | +|------------------------|--------------------|--------------------------------|----------------------------|---------| +| 拉起应用 | 设备正常运行 | - | 成功拉起应用,进入首页 | 是 | +| 进入AudioCapturer页面 | 位于主页 | 点击"跳转到开发音频录制功能页面" | 进入AudioCapturer测试页面 | 是 | +| 初始化音频采集器 | AudioCapturer页面 | 点击"初始化"按钮 | 音频采集器初始化成功 | 是 | +| 允许权限 | 权限弹窗出现 | 点击"允许"按钮 | 授权麦克风权限成功 | 是 | +| 开始录制音频 | 音频采集器已初始化 | 点击"开始录制"按钮 | 录制开始,状态变为录制中 | 是 | +| 查看采集器状态 | 录制进行中或已初始化 | 点击"查看状态"按钮 | 显示当前采集器状态 | 是 | +| 停止录制音频 | 录制进行中 | 点击"停止录制"按钮 | 录制停止,状态变为已停止 | 是 | +| 释放采集器资源 | 录制已停止 | 点击"释放资源"按钮 | 释放音频采集器资源 | 是 | +| 进入AudioLoopback页面 | 位于主页 | 点击"跳转到实现低时延耳返页面" | 进入AudioLoopback测试页面 | 是 | +| 创建音频返听实例 | AudioLoopback页面 | 点击"创建音频低时延耳返实例" | 成功创建AudioLoopback实例 | 是 | +| 设置返听音量 | 返听实例已创建 | 点击"设置音频返听音量"按钮 | 成功设置音频返听音量 | 是 | +| 设置混响模式 | 返听实例已创建 | 点击"设置混响模式"按钮 | 成功设置混响模式 | 是 | +| 设置均衡器类型 | 返听实例已创建 | 点击"设置均衡器类型"按钮 | 成功设置均衡器类型 | 是 | +| 查询返听状态 | 返听实例已创建 | 点击"查询返听状态"按钮 | 显示当前返听状态 | 是 | +| 查询混响模式 | 返听实例已创建 | 点击"查询混响模式"按钮 | 显示当前混响模式 | 是 | +| 查询均衡器类型 | 返听实例已创建 | 点击"查询均衡器类型"按钮 | 显示当前均衡器类型 | 是 | +| 启用音频返听 | 返听实例已创建 | 点击"启用音频返听"按钮 | 成功启用音频返听 | 是 | +| 禁用音频返听 | 返听已启用 | 点击"禁用音频返听"按钮 | 成功禁用音频返听 | 是 | +| 进入AudioStreamManager页面 | 位于主页 | 点击"跳转到音频录制流管理页面" | 进入AudioStreamManager测试页面 | 是 | +| 获取当前录制流信息 | AudioStreamManager页面 | 点击"获取当前录制流信息"按钮 | 显示当前录制流信息 | 是 | +| 进入麦克风页面 | 位于主页 | 点击"跳转到管理麦克风页面" | 进入麦克风管理测试页面 | 是 | +| 创建音量组管理器 | 麦克风页面 | 点击"创建音量组管理器"按钮 | 成功创建音量组管理器实例 | 是 | +| 查询麦克风静音状态 | 音量组管理器已创建 | 点击"查询麦克风是否静音"按钮 | 显示当前麦克风静音状态 | 是 | \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/capture.png b/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/capture.png new file mode 100644 index 0000000000000000000000000000000000000000..ad1c330d570e90083e05fa0839a5e7a75ceeba0e Binary files /dev/null and b/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/capture.png differ diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/group.png b/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/group.png new file mode 100644 index 0000000000000000000000000000000000000000..5ba7b997e29fa25f10e02a85527c39089af82df9 Binary files /dev/null and b/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/group.png differ diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/index.png b/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/index.png new file mode 100644 index 0000000000000000000000000000000000000000..f95a895857a51abd51d346e803d8b018dcd471ea Binary files /dev/null and b/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/index.png differ diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/loopback.png b/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/loopback.png new file mode 100644 index 0000000000000000000000000000000000000000..f75375f6d4a92383c328512cb45480f5d0ab33f5 Binary files /dev/null and b/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/loopback.png differ diff --git a/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/mac.png b/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/mac.png new file mode 100644 index 0000000000000000000000000000000000000000..92d99f41e3f2b48eb433aaaa894bbbc6d14b32c2 Binary files /dev/null and b/MediaKit/AudioKit/AudioCaptureSampleJS/screenshots/mac.png differ diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/.gitignore b/MediaKit/AudioKit/AudioRendererSampleJS/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/.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/MediaKit/AudioKit/AudioRendererSampleJS/AppScope/app.json5 b/MediaKit/AudioKit/AudioRendererSampleJS/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..02e14fba2e646d86166447488188b104168fecbc --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/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.myapplication", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:layered_image", + "label": "$string:app_name" + } +} diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/AppScope/resources/base/element/string.json b/MediaKit/AudioKit/AudioRendererSampleJS/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..1080233f01384411ec684b58955cb8808746fdd3 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "MyApplication" + } + ] +} diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/AppScope/resources/base/media/background.png b/MediaKit/AudioKit/AudioRendererSampleJS/AppScope/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/MediaKit/AudioKit/AudioRendererSampleJS/AppScope/resources/base/media/background.png differ diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/AppScope/resources/base/media/foreground.png b/MediaKit/AudioKit/AudioRendererSampleJS/AppScope/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9427585b36d14b12477435b6419d1f07b3e0bb Binary files /dev/null and b/MediaKit/AudioKit/AudioRendererSampleJS/AppScope/resources/base/media/foreground.png differ diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/AppScope/resources/base/media/layered_image.json b/MediaKit/AudioKit/AudioRendererSampleJS/AppScope/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/AppScope/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/MediaKit/AudioKit/AudioRendererSampleJS/README.md b/MediaKit/AudioKit/AudioRendererSampleJS/README.md new file mode 100644 index 0000000000000000000000000000000000000000..3f44638ac74abf789835d316525bbfb23ec23c3c --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/README.md @@ -0,0 +1,101 @@ +# 实现音频播放功能 + +## 介绍 + +本示例基于AudioRenderer、AudioHaptic、AVPlayer等能力,实现了音频播放、音振协同播放、音频播放流管理以及音播放量等功能,包含了功能调用接口的完整链路。 + +## 效果图预览 + +**图1**:首页 + +选择跳转到对应功能页面。 + + + +**图2**:音振协同播放页 + +- 点击'使用注册源注册'按钮,即可通过文件URI来注册资源。 +- 点击'使用描述符注册'按钮,即可通过文件描述符来注册资源。 +- 点击'音振协同播放开始'按钮,即可开启音频播放并同步开启振动。 +- 点击'音振协同播放停止'按钮,即可停止音频播放并同步停止振动。 +- 点击'音振协同播放释放'按钮,即可释放实例将已注册的音频及振动资源移除注册。 + + + +**图3**:音频播放页 + +- 点击'初始化'按钮,即可创建实例,设置监听事件。 +- 点击'开始播放'按钮,开始音频渲染。 +- 点击'暂停播放'按钮,暂停渲染。 +- 点击'停止播放'按钮,停止渲染。 +- 点击'释放资源'按钮,可以销毁实例,释放资源。 +- 点击'获取音频流音量'按钮,可以获取指定流类型的音量信息。 +- 点击'设置应用的音量'按钮,可以设置应用的音量。 +- 点击'设置音频流音量'按钮,可以设置音频流音量。 +- 点击'是否支持空间音频'按钮,可以查询设备是否支持空间音频渲染能力。 +- 点击'查询空间音频开关状态'按钮,可以查询当前发声设备的空间音频渲染效果开关状态。 +- 点击'订阅开关状态事件'按钮,可以订阅当前发声设备空间音频渲染效果的开关状态变化事件。 +- 点击'取消订阅'按钮,可以取消订阅当前发声设备空间音频渲染效果的开关状态变化事件。 +- 点击'查看播放的状态'按钮,可以直接查看音频播放的状态。 +- 点击'获取所有流信息'按钮,可以获取所有音频播放流的信息。 + + + +## 工程结构&模块类型 + +``` +├───entry/src/main/ets +│ ├───pages +│ │ └───Index.ets // 首页。 +│ │ └───Haptic.ets // 音振协同播放页。 +│ │ └───Renderer.ets // 音频播放页。 +└───entry/src/main/resources // 资源目录。 +``` + +## 具体实现 + +### 音振协同播放功能 +- 源码参考:[haptic.ets](entry/src/main/ets/pages/haptic.ets) +- 使用流程: + - 需申请`ohos.permission.VIBRATE`权限来获取振动权限保证音振协同可以正常进行。 + - 点击'使用注册源注册'按钮,调用`audioHapticManagerInstance.registerSource`,通过文件URI来注册振动资源。 + - 点击'使用描述符注册'按钮,调用`audioHapticManagerInstance.registerSourceFromFd`,通过文件描述符来注册振动资源,后续的音振协同播放都是根据该方法注册的资源进行。 + - 点击'音振协同播放开始'按钮,调用`audioHapticManagerInstance.start`,开启音频播放并同步开启振动。 + - 点击'音振协同播放停止'按钮,调用`audioHapticManagerInstance.stop`,停止音频播放并同步停止振动。 + - 点击'音振协同播放释放'按钮,调用`audioHapticManagerInstance.release`,销毁实例,释放资源。 + +### 音频播放功能 +- 源码参考:[renderer.ets](entry/src/main/ets/pages/renderer.ets) +- 使用流程: + - 点击'初始化'按钮,调用`audio.createAudioRenderer`,创建AudioRenderer实例。 + - 点击'开始播放'按钮,调用`audioRenderer.start`,渲染音频。 + - 点击'暂停播放'按钮,调用`audioRenderer.pause`,暂停渲染。 + - 点击'停止播放'按钮,调用`audioRenderer.stop`,停止渲染。 + - 点击'释放资源'按钮,调用`audioRenderer.release`,销毁实例,释放资源。 + - 点击'获取音频流音量'按钮,调用`audioVolumeManager.getVolumeByStream`、`audioVolumeManager.getMinVolumeByStream`、`audioVolumeManager.getMaxVolumeByStream`,获取指定流类型的音量信息,获取到的信息在日志信息栏下方打印。 + - 点击'设置应用的音量'按钮,调用`audioVolumeManager.setAppVolumePercentage`,设置应用的音量为20,音量的取值范围为[0, 100]。 + - 点击'设置音频流音量'按钮,调用`audioRenderer.setVolume`,设置音频流音量0.5,音量的取值范围为[0, 1]。 + - 点击'是否支持空间音频'按钮,调用`audioRoutingManager.getDevicesSync`,查询设备是否支持空间音频渲染能力,查询结果在日志信息栏下方打印。 + - 点击'查询空间音频开关状态'按钮,调用`audioSpatializationManager.isSpatializationEnabledForCurrentDevice`,查询当前发声设备的空间音频渲染效果开关状态,查询结果在日志信息栏下方打印。 + - 点击'订阅开关状态事件'按钮,调用`audioSpatializationManager.on`,监听当前发声设备空间音频渲染效果的开关状态变化事件,触发回调时在回调信息栏下方打印回调日志。 + - 点击'取消订阅'按钮,调用`audioSpatializationManager.off`,取消监听当前发声设备空间音频渲染效果的开关状态变化事件。 + - 点击'查看播放的状态'按钮,调用`audioRenderer.state`,获取播放流的状态,获取到的信息在日志信息栏下方打印。 + - 点击'获取所有流信息'按钮,调用`audioStreamManager.getCurrentAudioRendererInfoArray`,获取所有音频播放流的信息,获取到的信息在日志信息栏下方打印。 + +## 相关权限 + +振动权限:ohos.permission.VIBRATE + +## 模块依赖 + +不涉及。 + +## 约束与限制 + +1. 本示例支持在标准系统上运行,支持设备:RK3568。 + +2. 本示例支持API version 20,版本号: 6.0.0.43。 + +3. 本示例已支持使Build Version: 6.0.0.43, built on August 24, 2025。 + +4. 高等级APL特殊签名说明:无。 \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/build-profile.json5 b/MediaKit/AudioKit/AudioRendererSampleJS/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..3b9a7bcc04b86b38badd941e5a1109732f2ce493 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/build-profile.json5 @@ -0,0 +1,56 @@ +/* +* 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": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "targetSdkVersion": "6.0.0(20)", + "compatibleSdkVersion": "6.0.0(20)", + "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/MediaKit/AudioKit/AudioRendererSampleJS/code-linter.json5 b/MediaKit/AudioKit/AudioRendererSampleJS/code-linter.json5 new file mode 100644 index 0000000000000000000000000000000000000000..62baae473144070aa0fc532fce773652507047a1 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/code-linter.json5 @@ -0,0 +1,46 @@ +/* +* 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": { + "@security/no-unsafe-aes": "error", + "@security/no-unsafe-hash": "error", + "@security/no-unsafe-mac": "warn", + "@security/no-unsafe-dh": "error", + "@security/no-unsafe-dsa": "error", + "@security/no-unsafe-ecdsa": "error", + "@security/no-unsafe-rsa-encrypt": "error", + "@security/no-unsafe-rsa-sign": "error", + "@security/no-unsafe-rsa-key": "error", + "@security/no-unsafe-dsa-key": "error", + "@security/no-unsafe-dh-key": "error", + "@security/no-unsafe-3des": "error" + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/.gitignore b/MediaKit/AudioKit/AudioRendererSampleJS/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/build-profile.json5 b/MediaKit/AudioKit/AudioRendererSampleJS/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..311e89829c07d7e8c38b46bf8a0eed43b481c6bf --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/build-profile.json5 @@ -0,0 +1,58 @@ +/* +* 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": { + "resOptions": { + "copyCodeResource": { + "enable": false + } + }, + "externalNativeOptions": { + "path": "./src/main/cpp/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/MediaKit/AudioKit/AudioRendererSampleJS/entry/hvigorfile.ts b/MediaKit/AudioKit/AudioRendererSampleJS/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..61cca58be096424d17b6aadf3dff1c2ea06e05f3 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/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. */ +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/obfuscation-rules.txt b/MediaKit/AudioKit/AudioRendererSampleJS/entry/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/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/MediaKit/AudioKit/AudioRendererSampleJS/entry/oh-package.json5 b/MediaKit/AudioKit/AudioRendererSampleJS/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..ca96d0b71f5c111d387e65f10793fb84e3a016e2 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/oh-package.json5 @@ -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. +*/ +{ + "name": "entry", + "version": "1.0.0", + "description": "Please describe the basic information.", + "main": "", + "author": "", + "license": "", + "dependencies": { + "@ohos/hypium": "1.0.15", + "@ohos/hamock": "1.0.0" + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/entryability/EntryAbility.ets b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..4ae1807cdc1b76c6ef0123c184f575f74b21b8f6 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,62 @@ +/* +* 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 { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +const DOMAIN = 0x0000; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + try { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + } catch (err) { + hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err)); + } + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); + return; + } + hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..96164e538b152554e43e37a07d8ff4e7db326a0d --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,30 @@ +/* +* 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'; + +const DOMAIN = 0x0000; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(DOMAIN, 'testTag', 'onBackup ok'); + await Promise.resolve(); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + await Promise.resolve(); + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/pages/Index.ets b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..28fbfb144c6d23e20f4950b6ae6f58fac2e06704 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,54 @@ +/* +* 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 {Router} from '@ohos.arkui.UIContext'; +const router = new Router(); + +@Entry +@Component +struct Index { + build() { + Column() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center}) { + this.textComponent('Index'); + this.buttonComponent('音振协同播放页面', 'pages/Haptic'); + this.buttonComponent('音频播放页面', 'pages/Renderer'); + }.size({ width: '100%', height: '100%' }) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + } + + @Builder + textComponent(name: string) { + Text(name) + .textAlign(TextAlign.Center) + .margin(10) + .fontSize($r('app.float.index_text_font_size')) + .size({ width: '100%', height: $r('app.float.index_button_height_size') }) + } + + @Builder + buttonComponent(name: string, dstUri: string) { + Button(name) + .width('80%') + .margin(10) + .onClick((e: ClickEvent) => { + router.pushUrl({ url: dstUri }); + }) + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/pages/haptic.ets b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/pages/haptic.ets new file mode 100644 index 0000000000000000000000000000000000000000..11ac9317390a37e03fc85f0a8516d8668e7e643e --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/pages/haptic.ets @@ -0,0 +1,340 @@ +/* +* 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. +*/ + +// [Start get_haptic] +import { audio, audioHaptic } from '@kit.AudioKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { common } from '@kit.AbilityKit'; + +let audioHapticManagerInstance: audioHaptic.AudioHapticManager = audioHaptic.getAudioHapticManager(); + +// [StartExclude get_haptic] +// [Start create_haptic] +let options: audioHaptic.AudioHapticPlayerOptions = {muteAudio: false, muteHaptics: false}; +let audioHapticPlayer: audioHaptic.AudioHapticPlayer | undefined = undefined; +// [StartExclude create_haptic] +let idForFd = 0; + +// 全局UI更新回调 +let globalLogUpdate: ((msg: string, isError: boolean) => void) | undefined; + +async function registSource(){ + // [EndExclude get_haptic] + // 方法1:使用registerSource接口注册资源。 + let audioUri = 'data/audioTest.wav'; // 此处仅作示例,实际使用时需要将文件替换为应用目标音频资源的Uri。 + let hapticUri = 'data/hapticTest.json'; // 此处仅作示例,实际使用时需要将文件替换为应用目标振动资源的Uri。 + let idForUri = 0; + + audioHapticManagerInstance.registerSource(audioUri, hapticUri).then((value: number) => { + console.info(`Promise returned to indicate that the source id of the registered source ${value}.`); + idForUri = value; + // [StartExclude get_haptic] + if (globalLogUpdate) { + globalLogUpdate(`Promise returned to indicate that the source id of the registered source ${value}.`, false); + } + // [EndExclude get_haptic] + }).catch((err: BusinessError) => { + console.error(`Failed to register source ${err}`); + // [StartExclude get_haptic] + if (globalLogUpdate) { + globalLogUpdate(`Failed to register source ${err}`, true); + } + // [EndExclude get_haptic] + }); + // [StartExclude get_haptic] +} + +async function registWithFD(context: common.UIAbilityContext) { + // [EndExclude get_haptic] + // 方法2:使用registerSourceFromFd接口注册资源。 + // 此处仅作示例,实际使用时需要将文件替换为应用rawfile目录下的对应文件。 + let audioFile = context.resourceManager.getRawFdSync('audioTest.ogg'); + let audioFd: audioHaptic.AudioHapticFileDescriptor = { + fd: audioFile.fd, + offset: audioFile.offset, + length: audioFile.length, + }; + // 此处仅作示例,实际使用时需要将文件替换为应用rawfile目录下的对应文件。 + let hapticFile = context.resourceManager.getRawFdSync('hapticTest.json'); + let hapticFd: audioHaptic.AudioHapticFileDescriptor = { + fd: hapticFile.fd, + offset: hapticFile.offset, + length: hapticFile.length, + }; + audioHapticManagerInstance.registerSourceFromFd(audioFd, hapticFd).then((value: number) => { + console.info('Succeeded in doing registerSourceFromFd.'); + idForFd = value; + // [StartExclude get_haptic] + if (globalLogUpdate) { + globalLogUpdate('Succeeded in doing registerSourceFromFd.', false); + } + // [EndExclude get_haptic] + }).catch((err: BusinessError) => { + console.error(`Failed to registerSourceFromFd. Code: ${err.code}, message: ${err.message}`); + // [StartExclude get_haptic] + if (globalLogUpdate) { + globalLogUpdate(`Failed to registerSourceFromFd. Code: ${err.code}, message: ${err.message}`, true); + } + // [EndExclude get_haptic] + }); + // [End get_haptic] + // [Start set_hapticparam] + let latencyMode: audioHaptic.AudioLatencyMode = audioHaptic.AudioLatencyMode.AUDIO_LATENCY_MODE_FAST; + audioHapticManagerInstance.setAudioLatencyMode(idForFd, latencyMode); + + let usage: audio.StreamUsage = audio.StreamUsage.STREAM_USAGE_NOTIFICATION; + audioHapticManagerInstance.setStreamUsage(idForFd, usage); + // [End set_hapticparam] + // [EndExclude create_haptic] + audioHapticManagerInstance.createPlayer(idForFd, options).then((value: audioHaptic.AudioHapticPlayer) => { + console.info(`Create the audio haptic player successfully.`); + audioHapticPlayer = value; + // [StartExclude create_haptic] + if (globalLogUpdate) { + globalLogUpdate(`Create the audio haptic player successfully.`, false); + } + // [EndExclude create_haptic] + }).catch((err: BusinessError) => { + console.error(`Failed to create player ${err}`); + // [StartExclude create_haptic] + if (globalLogUpdate) { + globalLogUpdate(`Failed to create player ${err}`, true); + } + // [EndExclude create_haptic] + }); + // [End create_haptic] +} +async function start(){ + if (audioHapticPlayer == null){ + console.info(`haptic player hasn't been defined.`); + + if (globalLogUpdate) { + globalLogUpdate(`haptic player hasn't been defined.`, false); + } + } else { + // [Start haptic_start] + audioHapticPlayer.start().then(() => { + console.info(`Promise returned to indicate that start playing successfully.`); + // [StartExclude haptic_start] + if (globalLogUpdate) { + globalLogUpdate(`Promise returned to indicate that start playing successfully.`, false); + } + // [EndExclude haptic_start] + }).catch((err: BusinessError) => { + console.error(`Failed to start playing. ${err}`); + // [StartExclude haptic_start] + if (globalLogUpdate) { + globalLogUpdate(`Failed to start playing. ${err}`, true); + } + // [EndExclude haptic_start] + }); + // [End haptic_start] + } +} +async function stop(){ + if (audioHapticPlayer == null){ + console.info(`haptic player hasn't been defined.`); + + if (globalLogUpdate) { + globalLogUpdate(`haptic player hasn't been defined.`, false); + } + } else { + // [Start haptic_stop] + audioHapticPlayer.stop().then(() => { + console.info(`Promise returned to indicate that stop playing successfully.`); + // [StartExclude haptic_stop] + if (globalLogUpdate) { + globalLogUpdate(`Promise returned to indicate that stop playing successfully.`, false); + } + // [EndExclude haptic_stop] + }).catch((err: BusinessError) => { + console.error(`Failed to stop playing. ${err}`); + // [StartExclude haptic_stop] + if (globalLogUpdate) { + globalLogUpdate(`Failed to stop playing. ${err}`, true); + } + // [EndExclude haptic_stop] + }); + // [End haptic_stop] + } +} +async function release(){ + if(audioHapticPlayer == null){ + console.info(`haptic player hasn't been defined.`); + + if (globalLogUpdate) { + globalLogUpdate(`haptic player hasn't been defined.`, false); + } + } else { + // [Start haptic_release] + audioHapticPlayer.release().then(() => { + console.info(`Promise returned to indicate that release the audio haptic player successfully.`); + // [StartExclude haptic_release] + if (globalLogUpdate) { + globalLogUpdate(`Promise returned to indicate that release the audio haptic player successfully.`, false); + } + // [EndExclude haptic_release] + }).catch((err: BusinessError) => { + console.error(`Failed to release the audio haptic player. ${err}`); + // [StartExclude haptic_release] + if (globalLogUpdate) { + globalLogUpdate(`Failed to release the audio haptic player. ${err}`, true); + } + // [EndExclude haptic_release] + }); + // [End haptic_release] + // [Start haptic_unregist] + audioHapticManagerInstance.unregisterSource(idForFd).then(() => { + console.info(`Promise returned to indicate that unregister source successfully`); + // [StartExclude haptic_unregist] + if (globalLogUpdate) { + globalLogUpdate(`Promise returned to indicate that unregister source successfully`, false); + } + // [EndExclude haptic_unregist] + }).catch((err: BusinessError) => { + console.error(`Failed to unregister source ${err}`); + // [StartExclude haptic_unregist] + if (globalLogUpdate) { + globalLogUpdate(`Failed to unregister source ${err}`, true); + } + // [EndExclude haptic_unregist] + }); + // [End haptic_unregist] + } +} + +@Entry +@Component +struct Index { + @State logMessages: string = '暂无日志信息'; + + aboutToAppear(): void { + // 设置全局回调 + globalLogUpdate = (msg, isError) => this.updateLogInfo(msg, isError); + } + + // 更新日志信息 + updateLogInfo(msg: string, isError: boolean): void { + const timestamp = new Date().toLocaleTimeString(); + const prefix = isError ? '[ERROR]' : '[INFO]'; + this.logMessages = `[${timestamp}] ${prefix} ${msg}`; + } + + build(): void { + Scroll() { + Column() { + // 信息显示区域 + Column() { + Text('日志信息') + .fontSize(18) + .fontWeight(FontWeight.Medium) + .margin({ bottom: 8 }) + + Scroll() { + Text(this.logMessages) + .fontSize(12) + .fontColor('#52C41A') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .height(80) + .width('80%') + } + .width('100%') + .padding(8) + .backgroundColor(Color.White) + .borderRadius(12) + .margin({ bottom: 16 }) + + // 功能按钮 + Row() { + Column() { + Text('使用注册源注册').fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + } + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async (): Promise => { + await registSource() + }); + + Column() { + Text('使用描述符注册').fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + } + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + // 请在组件内获取context,确保this.getUIContext().getHostContext()返回结果为UIAbilityContext。 + let context = this.getUIContext().getHostContext() as common.UIAbilityContext; + await registWithFD(context); + }); + } + + Row() { + Column() { + Text('音振协同播放开始').fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + } + .id('audio_effect_manager_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async (): Promise => { + await start() + }); + + Column() { + Text('音振协同播放停止').fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + } + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + await stop() + }); + } + + Row() { + Column() { + Text('音振协同播放释放').fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + } + .id('audio_volume_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async (): Promise => { + await release() + }); + } + .padding(12) + } + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5'); + } + } +} diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/pages/renderer.ets b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/pages/renderer.ets new file mode 100644 index 0000000000000000000000000000000000000000..e963fe3390fe0483862e26f6cf15af91c2a1f59c --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/ets/pages/renderer.ets @@ -0,0 +1,854 @@ +/* +* 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. +*/ + +// [Start render_process] +// [Start init_callback] +// [Start create_audiorender] +// [Start get_volumemanager] +// [Start get_systemvolume] +// [Start regist_volumechangecallback] +// [Start set_appvolume] +// [Start get_spacesound] +// [Start check_spacesound] +// [Start check_isspacesoundon] +// [Start regist_spacesoundcallback] +// [Start unregist_spacesoundcallback] +// [Start check_renderstate] +// [Start regist_listeningrendererchange] +// [Start create_streammanager] +// [Start regist_renderchangechallback] +// [Start get_allstreaminfo] +import { audio } from '@kit.AudioKit'; +// [StartExclude get_allstreaminfo] +// [StartExclude regist_renderchangechallback] +// [StartExclude create_streammanager] +// [StartExclude regist_listeningrendererchange] +// [StartExclude check_renderstate] +// [StartExclude unregist_spacesoundcallback] +// [StartExclude regist_spacesoundcallback] +// [StartExclude check_isspacesoundon] +// [StartExclude check_spacesound] +// [StartExclude get_spacesound] +// [StartExclude set_appvolume] +// [StartExclude regist_volumechangecallback] +// [StartExclude get_volumemanager] +// [StartExclude create_audiorender] +// [StartExclude get_volumemanager] +// [Start render_start] +// [Start render_stop] +// [Start render_release] +// [Start Renderset_streamvolume] +// [EndExclude get_allstreaminfo] +import { BusinessError } from '@kit.BasicServicesKit'; +// [StartExclude get_allstreaminfo] +// [StartExclude Renderset_streamvolume] +// [StartExclude get_systemvolume] +// [StartExclude render_start] +// [StartExclude render_stop] +// [StartExclude render_release] +import { fileIo as fs } from '@kit.CoreFileKit'; +import { common } from '@kit.AbilityKit'; +// [StartExclude render_process] +// [StartExclude init_callback] +import { media } from '@kit.MediaKit'; +// [EndExclude get_volumemanager] +// [EndExclude set_appvolume] +// [EndExclude get_spacesound] +// [EndExclude create_streammanager] +let audioManager = audio.getAudioManager(); +// [StartExclude create_streammanager] +// [StartExclude get_spacesound] +let audioVolumeManager = audioManager.getVolumeManager(); +// [StartExclude set_appvolume] +// [End get_volumemanager] +let avPlayer: media.AVPlayer; +media.createAVPlayer((error: BusinessError, video: media.AVPlayer) => { + if (video != null) { + avPlayer = video; + console.info('Succeeded in creating AVPlayer'); + } else { + console.error(`Failed to create AVPlayer, error message:${error.message}`); + } +}); +// [EndExclude create_streammanager] +let audioStreamManager = audioManager.getStreamManager(); +// [End create_streammanager] +// [EndExclude get_spacesound] +let audioSpatializationManager = audioManager.getSpatializationManager(); +// [End get_spacesound] +// [EndExclude check_spacesound] +let audioRoutingManager = audioManager.getRoutingManager(); +// [StartExclude check_spacesound] +// [EndExclude render_process] +const TAG = 'AudioRendererDemo'; +// [EndExclude init_callback] +class Options { + offset?: number; + length?: number; +} + +// [StartExclude render_process] +// 全局变量用于UI更新回调 +let globalLogUpdate: (msg: string, isError: boolean) => void = () => {}; +let globalStateUpdate: (msg: string) => void = () => {}; +let globalCallbackUpdate: (msg: string) => void = () => {}; +// [EndExclude render_process] + +// [StartExclude init_callback] +let audioRenderer: audio.AudioRenderer | undefined = undefined; +// [EndExclude create_audiorender] +let audioStreamInfo: audio.AudioStreamInfo = { + samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率。 + channels: audio.AudioChannel.CHANNEL_2, // 通道。 + sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式。 + encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式。 +}; +let audioRendererInfo: audio.AudioRendererInfo = { + usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 音频流使用类型:音乐。根据业务场景配置,参考StreamUsage。 + rendererFlags: 0 // 音频渲染器标志。 +}; +let audioRendererOptions: audio.AudioRendererOptions = { + streamInfo: audioStreamInfo, + rendererInfo: audioRendererInfo +}; +// [StartExclude create_audiorender] +let writeDataCallback: audio.AudioRendererWriteDataCallback; + +async function initArguments(context: common.UIAbilityContext) { + // [EndExclude init_callback] + let bufferSize: number = 0; + let file = await context.resourceManager.getRawFd('32_xiyouji.pcm'); + writeDataCallback = (buffer: ArrayBuffer) => { + let options: Options = { + offset: bufferSize, + length: buffer.byteLength + }; + // [StartExclude init_callback] + + try { + let bufferLength = fs.readSync(file.fd, buffer, options); + bufferSize += buffer.byteLength; + // 如果当前回调传入的数据不足一帧,空白区域需要使用静音数据填充,否则会导致播放出现杂音。 + if (bufferLength < buffer.byteLength) { + let view = new DataView(buffer); + for (let i = bufferLength; i < buffer.byteLength; i++) { + // 空白区域填充静音数据。当使用音频采样格式为SAMPLE_FORMAT_U8时0x7F为静音数据,使用其他采样格式时0为静音数据。 + view.setUint8(i, 0); + } + } + // API version 11不支持返回回调结果,从API version 12开始支持返回回调结果。 + // 如果开发者不希望播放某段buffer,返回audio.AudioDataCallbackResult.INVALID即可。 + return audio.AudioDataCallbackResult.VALID; + } catch (error) { + console.error('Error reading file:', error); + // [StartExclude render_process] + globalLogUpdate('Error reading file: ' + error, true); + // [EndExclude render_process] + // API version 11不支持返回回调结果,从API version 12开始支持返回回调结果。 + return audio.AudioDataCallbackResult.INVALID; + } + }; +} + +// 初始化,创建实例,设置监听事件。 +async function init() { + // [EndExclude create_audiorender] + audio.createAudioRenderer(audioRendererOptions, (err, renderer) => { // 创建AudioRenderer实例。 + if (!err) { + console.info(`${TAG}: creating AudioRenderer success`); + // [StartExclude create_audiorender] + // [StartExclude render_process] + globalLogUpdate(`${TAG}: creating AudioRenderer success`, false); + // [EndExclude render_process] + // [EndExclude create_audiorender] + audioRenderer = renderer; + if (audioRenderer !== undefined) { + // [EndExclude init_callback] + audioRenderer.on('writeData', writeDataCallback); + // [End init_callback] + // [StartExclude render_process] + // [StartExclude create_audiorender] + // 自动启动监听 + registListeningStateChange(); + listenvolume(); + listenRenderChange(); + // [EndExclude render_process] + // [EndExclude create_audiorender] + } + } else { + console.info(`${TAG}: creating AudioRenderer failed, error: ${err.message}`); + // [StartExclude render_process] + globalLogUpdate(`${TAG}: creating AudioRenderer failed, error: ${err.message}`, false); + // [EndExclude render_process] + } + }); + // [End create_audiorender] +} + +// 开始一次音频渲染。 +async function start() { + if (audioRenderer !== undefined) { + let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED]; + if (stateGroup.indexOf(audioRenderer.state.valueOf()) === -1) { // 当且仅当状态为prepared、paused和stopped之一时才能启动渲染。 + console.error(TAG + 'start failed'); + // [StartExclude render_process] + globalLogUpdate(TAG + 'start failed', true); + // [EndExclude render_process] + return; + } + // 启动渲染。 + // [EndExclude render_start] + audioRenderer.start((err: BusinessError) => { + if (err) { + console.error('Renderer start failed.'); + // [StartExclude render_start] + // [StartExclude render_process] + globalLogUpdate('Renderer start failed.', true); + // [EndExclude render_process] + // [EndExclude render_start] + } else { + console.info('Renderer start success.'); + // [StartExclude render_start] + // [StartExclude render_process] + globalLogUpdate('Renderer start success.', false); + // [EndExclude render_process] + // [EndExclude render_start] + } + }); + // [End render_start] + } +} + +async function pause() { + // 暂停渲染。 + if (audioRenderer !== undefined) { + // 只有渲染器状态为running的时候才能暂停。 + if (audioRenderer.state.valueOf() !== audio.AudioState.STATE_RUNNING) { + console.info('Renderer is not running'); + // [StartExclude render_process] + globalLogUpdate('Renderer is not running', false); + // [EndExclude render_process] + return; + } + // 暂停渲染。 + audioRenderer.pause((err: BusinessError) => { + if (err) { + console.error('Renderer pause failed.'); + // [StartExclude render_process] + globalLogUpdate('Renderer pause failed.', true); + // [EndExclude render_process] + } else { + console.info('Renderer pause success.'); + // [StartExclude render_process] + globalLogUpdate('Renderer pause success.', false); + // [EndExclude render_process] + } + }); + } +} + +// 停止渲染。 +async function stop() { + if (audioRenderer !== undefined) { + // 只有渲染器状态为running或paused的时候才可以停止。 + if (audioRenderer.state.valueOf() !== audio.AudioState.STATE_RUNNING && + audioRenderer.state.valueOf() !== audio.AudioState.STATE_PAUSED) { + console.info('Renderer is not running or paused.'); + // [StartExclude render_process] + globalLogUpdate('Renderer is not running or paused.', false); + // [EndExclude render_process] + return; + } + // 停止渲染。 + // [EndExclude render_stop] + audioRenderer.stop((err: BusinessError) => { + if (err) { + console.error('Renderer stop failed.'); + // [StartExclude render_stop] + // [StartExclude render_process] + globalLogUpdate('Renderer stop failed.', true); + // [EndExclude render_process] + // [EndExclude render_stop] + } else { + console.info('Renderer stop success.'); + // [StartExclude render_stop] + // [StartExclude render_process] + globalLogUpdate('Renderer stop success.', false); + // [EndExclude render_process] + // [EndExclude render_stop] + } + }); + // [End render_stop] + } +} + +// 销毁实例,释放资源。 +async function release() { + if (audioRenderer !== undefined) { + // 渲染器状态不是released状态,才能release。 + if (audioRenderer.state.valueOf() === audio.AudioState.STATE_RELEASED) { + console.info('Renderer already released'); + // [StartExclude render_process] + globalLogUpdate('Renderer already released', false); + // [EndExclude render_process] + return; + } + + // [StartExclude render_process] + // 先取消所有监听 + offlistenRenderChange(); + audioVolumeManager.off('streamVolumeChange'); + console.info('Volume change listener removed'); + globalLogUpdate('Volume change listener removed', false); + // [EndExclude render_process] + + // 释放资源。 + // [EndExclude render_release] + audioRenderer.release((err: BusinessError) => { + if (err) { + console.error('Renderer release failed.'); + // [StartExclude render_release] + // [StartExclude render_process] + globalLogUpdate('Renderer release failed.', true); + // [EndExclude render_process] + // [EndExclude render_release] + } else { + // 关闭沙箱文件 + console.info('Renderer release success.'); + // [StartExclude render_release] + // [StartExclude render_process] + globalLogUpdate('Renderer release success.', false); + // [EndExclude render_process] + // [EndExclude render_release] + } + }); + // [End render_release] + } +} +// [StartExclude render_process] +async function getvolume(){ + // [EndExclude get_systemvolume] + // 获取指定流的音量。 + audioVolumeManager.getVolumeByStream(audio.StreamUsage.STREAM_USAGE_MUSIC); + // [StartExclude get_systemvolume] + // [EndExclude get_systemvolume] + // 获取指定流的最小音量。 + audioVolumeManager.getMinVolumeByStream(audio.StreamUsage.STREAM_USAGE_MUSIC); + + // 获取指定流的最大音量。 + audioVolumeManager.getMaxVolumeByStream(audio.StreamUsage.STREAM_USAGE_MUSIC); + // [End get_systemvolume] +} + +async function listenvolume(){ + // [EndExclude regist_volumechangecallback] + audioVolumeManager.on('streamVolumeChange', audio.StreamUsage.STREAM_USAGE_MUSIC, + (streamVolumeEvent: audio.StreamVolumeEvent) => { + console.info(`StreamUsagem: ${streamVolumeEvent.streamUsage} `); + console.info(`Volume level: ${streamVolumeEvent.volume} `); + console.info(`Whether to updateUI: ${streamVolumeEvent.updateUi} `); + // [StartExclude regist_volumechangecallback] + globalCallbackUpdate(`StreamUsagem: ${streamVolumeEvent.streamUsage} Volume level: + ${streamVolumeEvent.volume} Whether to updateUI: ${streamVolumeEvent.updateUi}`); + // [EndExclude regist_volumechangecallback] + }); + // [End regist_volumechangecallback] +} + +async function setAPPvolume(){ + // [EndExclude set_appvolume] + // 设置应用的音量(范围为0到100)。 + audioVolumeManager.setAppVolumePercentage(20).then(() => { + console.info(`set app volume success.`); + // [StartExclude set_appvolume] + globalLogUpdate('set app volume success.', false); + // [EndExclude set_appvolume] + }); + + // 查询应用音量。 + audioVolumeManager.getAppVolumePercentage().then((value: number) => { + console.info(`app volume is ${value}.`); + // [StartExclude set_appvolume] + globalLogUpdate(`app volume is ${value}.`, false); + // [EndExclude set_appvolume] + }); + + // 监听应用音量变化,on方法和off方法传入callback参数一致,off方法取消对应on方法订阅的监听。 + let appVolumeChangeCallback = (volumeEvent: audio.VolumeEvent) => { + console.info(`VolumeType of stream: ${volumeEvent.volumeType} `); + console.info(`Volume level: ${volumeEvent.volume} `); + console.info(`Whether to updateUI: ${volumeEvent.updateUi} `); + // [StartExclude set_appvolume] + globalCallbackUpdate(`VolumeType of stream: ${volumeEvent.volumeType} Volume level: + ${volumeEvent.volume} Whether to updateUI: ${volumeEvent.updateUi}`); + // [EndExclude set_appvolume] + }; + audioVolumeManager.on('appVolumeChange', appVolumeChangeCallback); + audioVolumeManager.off('appVolumeChange', appVolumeChangeCallback); + // [End set_appvolume] +} + +async function setStreamVolume(){ + if (audioRenderer) { + // [Start AVPlayerset_streamvolume] + let volume = 1.0; // 指定的音量大小,取值范围为[0.00-1.00],1表示最大音量。 + avPlayer.setVolume(volume); + // [End AVPlayerset_streamvolume] + // [EndExclude Renderset_streamvolume] + // 设置音频流音量。 + audioRenderer.setVolume(0.5).then(() => { // 音量范围为[0.0-1.0]。 + console.info('Invoke setVolume succeeded.'); + // [StartExclude Renderset_streamvolume] + globalLogUpdate('Invoke setVolume succeeded.', false); + // [EndExclude Renderset_streamvolume] + }).catch((err: BusinessError) => { + console.error(`Invoke setVolume failed, code is ${err.code}, message is ${err.message}`); + // [StartExclude Renderset_streamvolume] + globalLogUpdate(`Invoke setVolume failed, code is ${err.code}, message is ${err.message}`, true); + // [EndExclude Renderset_streamvolume] + }); + + // 获取音频流音量。 + try { + let value: number = audioRenderer.getVolume(); + console.info(`Indicate that the volume is obtained ${value}.`); + // [StartExclude Renderset_streamvolume] + globalLogUpdate(`Indicate that the volume is obtained ${value}.`, false); + // [EndExclude Renderset_streamvolume] + } catch (err) { + let error = err as BusinessError; + console.error(`Failed to obtain the volume, error ${error}.`); + // [StartExclude Renderset_streamvolume] + globalLogUpdate(`Failed to obtain the volume, error ${error}.`, true); + // [EndExclude Renderset_streamvolume] + } + // [End Renderset_streamvolume] + } else { + console.info('renderer is not created.'); + globalLogUpdate('renderer is not created.', false); + } +} + +async function getDevicesSync(){ + // [EndExclude check_spacesound] + let deviceDescriptors = audioRoutingManager.getDevicesSync(audio.DeviceFlag.OUTPUT_DEVICES_FLAG); + console.info(`Succeeded in getting devices, AudioDeviceDescriptors: ${JSON.stringify(deviceDescriptors)}.`); + // [End check_spacesound] + globalLogUpdate(`Succeeded in getting devices, AudioDeviceDescriptors: ${JSON.stringify(deviceDescriptors)}.`, false); +} + +async function getStatus(){ + // [EndExclude check_isspacesoundon] + let isSpatializationEnabledForCurrentDevice = audioSpatializationManager.isSpatializationEnabledForCurrentDevice(); + console.info(`Succeeded in using isSpatializationEnabledForCurrentDevice function, + IsSpatializationEnabledForCurrentDevice: ${isSpatializationEnabledForCurrentDevice}.`); + // [End check_isspacesoundon] + globalLogUpdate(`Succeeded in using isSpatializationEnabledForCurrentDevice function, + IsSpatializationEnabledForCurrentDevice: ${isSpatializationEnabledForCurrentDevice}.`, false); +} + +async function on(){ + // [EndExclude regist_spacesoundcallback] + audioSpatializationManager.on('spatializationEnabledChangeForCurrentDevice', + (isSpatializationEnabledForCurrentDevice: boolean) => { + console.info(`Succeeded in using on function, IsSpatializationEnabledForCurrentDevice: + ${isSpatializationEnabledForCurrentDevice}.`); + // [StartExclude regist_spacesoundcallback] + globalCallbackUpdate(`Succeeded in using on function, IsSpatializationEnabledForCurrentDevice: + ${isSpatializationEnabledForCurrentDevice}.`); + // [EndExclude regist_spacesoundcallback] + }); + // [End regist_spacesoundcallback] +} + +async function off() { + // [EndExclude unregist_spacesoundcallback] + audioSpatializationManager.off('spatializationEnabledChangeForCurrentDevice'); + // [End unregist_spacesoundcallback] +} + +async function getRendererState(){ + if (audioRenderer != null) { + // [EndExclude check_renderstate] + let audioRendererState: audio.AudioState = audioRenderer.state; + console.info(`Current state is: ${audioRendererState }`); + // [End check_renderstate] + globalLogUpdate(`Current state is: ${audioRendererState }`, false); + } else { + console.info(`audio renderer is null`) + globalLogUpdate('audio renderer is null', false); + } +} + +async function registListeningStateChange() { + if (audioRenderer != null) { + // [EndExclude regist_listeningrendererchange] + audioRenderer.on('stateChange', (rendererState: audio.AudioState) => { + console.info(`State change to: ${rendererState}`); + // [StartExclude regist_listeningrendererchange] + globalCallbackUpdate(`State change to: ${rendererState}`); + // [EndExclude regist_listeningrendererchange] + }); + // [End regist_listeningrendererchange] + } else { + console.info(`audio renderer is null`) + globalLogUpdate('audio renderer is null', false); + } +} + +async function listenRenderChange() { + // [EndExclude regist_renderchangechallback] + audioStreamManager.on('audioRendererChange', (AudioRendererChangeInfoArray: audio.AudioRendererChangeInfoArray) => { + for (let i = 0; i < AudioRendererChangeInfoArray.length; i++) { + let AudioRendererChangeInfo = AudioRendererChangeInfoArray[i]; + console.info(`## RendererChange on is called for ${i} ##`); + console.info(`StreamId for ${i} is: ${AudioRendererChangeInfo.streamId}`); + console.info(`Content ${i} is: ${AudioRendererChangeInfo.rendererInfo.content}`); + console.info(`Stream ${i} is: ${AudioRendererChangeInfo.rendererInfo.usage}`); + console.info(`Flag ${i} is: ${AudioRendererChangeInfo.rendererInfo.rendererFlags}`); + // [StartExclude regist_renderchangechallback] + globalCallbackUpdate(`## RendererChange on is called for ${i} ## StreamId: + ${AudioRendererChangeInfo.streamId} Content: ${AudioRendererChangeInfo.rendererInfo.content} Stream: + ${AudioRendererChangeInfo.rendererInfo.usage} Flag: ${AudioRendererChangeInfo.rendererInfo.rendererFlags}`); + // [EndExclude regist_renderchangechallback] + for (let j = 0;j < AudioRendererChangeInfo.deviceDescriptors.length; j++) { + console.info(`Id: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].id}`); + console.info(`Type: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceType}`); + console.info(`Role: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceRole}`); + console.info(`Name: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].name}`); + console.info(`Address: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].address}`); + console.info(`SampleRates: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].sampleRates[0]}`); + console.info(`ChannelCount ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelCounts[0]}`); + console.info(`ChannelMask: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelMasks}`); + } + } + }); + // [End regist_renderchangechallback] +} + +async function offlistenRenderChange(){ + // [Start unregist_renderchangechallback] + audioStreamManager.off('audioRendererChange'); + console.info('RendererChange Off is called '); + // [End unregist_renderchangechallback] + globalLogUpdate('RendererChange Off is called ', false); +} + +// [EndExclude get_allstreaminfo] +async function getCurrentAudioRendererInfoArray(): Promise { + await audioStreamManager.getCurrentAudioRendererInfoArray() + .then((AudioRendererChangeInfoArray: audio.AudioRendererChangeInfoArray) => { + console.info(`getCurrentAudioRendererInfoArray Get Promise is called `); + // [StartExclude get_allstreaminfo] + globalLogUpdate('getCurrentAudioRendererInfoArray Get Promise is called ', false); + // [EndExclude get_allstreaminfo] + if (AudioRendererChangeInfoArray != null) { + for (let i = 0; i < AudioRendererChangeInfoArray.length; i++) { + let AudioRendererChangeInfo = AudioRendererChangeInfoArray[i]; + console.info(`StreamId for ${i} is: ${AudioRendererChangeInfo.streamId}`); + console.info(`Content ${i} is: ${AudioRendererChangeInfo.rendererInfo.content}`); + console.info(`Stream ${i} is: ${AudioRendererChangeInfo.rendererInfo.usage}`); + console.info(`Flag ${i} is: ${AudioRendererChangeInfo.rendererInfo.rendererFlags}`); + // [StartExclude get_allstreaminfo] + globalLogUpdate(`RendererInfo [${i}] - StreamId: ${AudioRendererChangeInfo.streamId}, + Content: ${AudioRendererChangeInfo.rendererInfo.content}, + Usage: ${AudioRendererChangeInfo.rendererInfo.usage}, + Flags: ${AudioRendererChangeInfo.rendererInfo.rendererFlags}`, false); + // [EndExclude get_allstreaminfo] + for (let j = 0;j < AudioRendererChangeInfo.deviceDescriptors.length; j++) { + console.info(`Id: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].id}`); + console.info(`Type: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceType}`); + console.info(`Role: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].deviceRole}`); + console.info(`Name: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].name}`); + console.info(`Address: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].address}`); + console.info(`SampleRates: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].sampleRates[0]}`); + console.info(`ChannelCount ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelCounts[0]}`); + console.info(`ChannelMask: ${i} : ${AudioRendererChangeInfo.deviceDescriptors[j].channelMasks}`); + } + } + } + }).catch((err: BusinessError ) => { + console.error(`Invoke getCurrentAudioRendererInfoArray failed, code is ${err.code}, message is ${err.message}`); + // [StartExclude get_allstreaminfo] + globalLogUpdate(`Invoke getCurrentAudioRendererInfoArray failed, code is + ${err.code}, message is ${err.message}`, true); + // [EndExclude get_allstreaminfo] + }); +} +// [End get_allstreaminfo] +// [End render_process] +@Entry +@Component +struct Index { + @State currentState: string = '当前状态: 未初始化'; + @State logMessages: string = '暂无日志'; + @State callbackMessages: string = '暂无回调信息'; + + aboutToAppear() { + globalLogUpdate = (msg: string, isError: boolean) => { + this.logMessages = msg; + }; + globalStateUpdate = (msg: string) => { + this.currentState = msg; + }; + globalCallbackUpdate = (msg: string) => { + this.callbackMessages = msg; + }; + } + + build() { + Scroll() { + Column() { + // 信息显示区域 + Column() { + Text('当前状态').fontSize(14).fontColor('#999999').margin({ bottom: 8 }) + Text(this.currentState).fontSize(14).fontColor('#007DFF') + } + .width('95%') + .padding(12) + .backgroundColor('#F5F5F5') + .borderRadius(8) + .margin({ bottom: 12 }) + .alignItems(HorizontalAlign.Start) + + Column() { + Text('日志信息').fontSize(14).fontColor('#999999').margin({ bottom: 8 }) + Text(this.logMessages) + .fontSize(14) + .fontColor(this.logMessages.includes('Failed') || this.logMessages.includes('failed') || + this.logMessages.includes('error') || this.logMessages.includes('Error') ? '#FF0000' : '#52C41A') + } + .width('95%') + .padding(12) + .backgroundColor('#F5F5F5') + .borderRadius(8) + .margin({ bottom: 12 }) + .alignItems(HorizontalAlign.Start) + + Column() { + Text('回调信息').fontSize(14).fontColor('#999999').margin({ bottom: 8 }) + Text(this.callbackMessages).fontSize(14).fontColor('#FA8C16') + } + .width('95%') + .padding(12) + .backgroundColor('#F5F5F5') + .borderRadius(8) + .margin({ bottom: 16 }) + .alignItems(HorizontalAlign.Start) + + Row() { + Column() { + Text('初始化').fontColor(Color.Black).fontSize(16).margin({ top: 12 }) + } + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + let context = this.getUIContext().getHostContext() as common.UIAbilityContext; + initArguments(context); + init(); + this.currentState = "已初始化"; + }); + + Column() { + Text('开始播放').fontColor(Color.Black).fontSize(16).margin({ top: 12 }) + } + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ bottom: 12 }) + .onClick(async () => { + start(); + this.currentState = "已开始"; + }); + } + + Row() { + Column() { + Text('暂停播放').fontColor(Color.Black).fontSize(16).margin({ top: 12 }) + } + .id('audio_effect_manager_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + pause(); + this.currentState = "已暂停"; + }); + + Column() { + Text('停止播放').fontColor(Color.Black).fontSize(16).margin({ top: 12 }) + } + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ bottom: 12 }) + .onClick(async () => { + stop(); + this.currentState = "已停止"; + }); + } + + Row() { + Column() { + Text('释放资源').fontColor(Color.Black).fontSize(16).margin({ top: 12 }) + } + .id('audio_volume_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + release(); + this.currentState = "已释放"; + }); + + Column() { + Text('获取音频流音量').fontColor(Color.Black).fontSize(16).margin({ top: 12 }) + } + .id('audio_volume_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + getvolume(); + }); + } + + Row() { + Column() { + Text('设置应用的音量').fontColor(Color.Black).fontSize(16).margin({ top: 12 }) + } + .id('audio_volume_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + setAPPvolume(); + }); + Column() { + Text('设置音频流音量').fontColor(Color.Black).fontSize(16).margin({ top: 12 }) + } + .id('audio_volume_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + setStreamVolume(); + }); + } + + Row() { + Column() { + Text('是否支持空间音频').fontColor(Color.Black).fontSize(16).margin({ top: 12 }) + } + .id('audio_volume_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + getDevicesSync(); + }); + Column() { + Text('查询空间音频开关状态').fontColor(Color.Black).fontSize(16).margin({ top: 12 }) + } + .id('audio_volume_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + getStatus(); + }); + } + + Row() { + Column() { + Text('订阅开关状态事件').fontColor(Color.Black).fontSize(16).margin({ top: 12 }) + } + .id('audio_volume_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + on(); + }); + Column() { + Text('取消订阅').fontColor(Color.Black).fontSize(16).margin({ top: 12 }) + } + .id('audio_volume_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + off(); + }); + } + + Row() { + Column() { + Text('查看播放的状态').fontColor(Color.Black).fontSize(16).margin({ top: 12 }) + } + .id('audio_volume_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + getRendererState(); + }); + + Column() { + Text('获取所有流信息').fontColor(Color.Black).fontSize(16).margin({ top: 12 }) + } + .id('audio_volume_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('5%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + getCurrentAudioRendererInfoArray(); + }); + } + // [EndExclude render_process] + } + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5'); + } + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/module.json5 b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..fd879dd24d231426050cdb13604481c605e56934 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/module.json5 @@ -0,0 +1,74 @@ +/* +* 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": [ + "default" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "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": [ + "ohos.want.action.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ], + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.VIBRATE", + "reason": "$string:app_name", + "usedScene": { + "abilities": ["VoIPCallAbility"], + "when": "always" + } + }, + ] + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/element/color.json b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/element/float.json b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..801bc27a39ab78e230cb049d46c0b4f7acb5e01d --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/element/float.json @@ -0,0 +1,16 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + }, + { + "name": "index_text_font_size", + "value": "50fp" + }, + { + "name": "index_button_height_size", + "value": "80vp" + } + ] +} diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/element/string.json b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f94595515a99e0c828807e243494f57f09251930 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/media/background.png b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/media/background.png differ diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/media/foreground.png b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97014d3e10e5ff511409c378cd4255713aecd85f Binary files /dev/null and b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/media/foreground.png differ diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/media/layered_image.json b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/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/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/media/startIcon.png b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/media/startIcon.png differ diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/profile/backup_config.json b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/profile/main_pages.json b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..3dfda59c40cdc433f75369e956fadf67695ded40 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,7 @@ +{ + "src": [ + "pages/Index", + "pages/haptic", + "pages/renderer" + ] +} diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/dark/element/color.json b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/dark/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..79b11c2747aec33e710fd3a7b2b3c94dd9965499 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/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/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/rawfile/audioTest.ogg b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/rawfile/audioTest.ogg new file mode 100644 index 0000000000000000000000000000000000000000..55abd1449cff747835369a358e72e33ab4cbddd3 Binary files /dev/null and b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/rawfile/audioTest.ogg differ diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/rawfile/hapticTest.json b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/rawfile/hapticTest.json new file mode 100644 index 0000000000000000000000000000000000000000..f94595515a99e0c828807e243494f57f09251930 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/main/resources/rawfile/hapticTest.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..621da0d393a21467d95052cf350dd15f438996e8 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,49 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/ets/test/List.test.ets b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..35adf701c182b5998567a2391e9169622344888d --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,9 @@ +import abilityTest from './Ability.test'; +import AudioHapticManagerTest from './Haptic.test'; +import AudioRendererDemoTest from './Renderer.test'; + +export default function testsuite() { + abilityTest(); + AudioHapticManagerTest(); + AudioRendererDemoTest(); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/ets/test/haptic.test.ets b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/ets/test/haptic.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..acdf53a89b8bc4f7177a7efc1be94b84f95d6a2f --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/ets/test/haptic.test.ets @@ -0,0 +1,170 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, Level } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const TAG = '[AudioHapticDemoTest]'; +const DOMAIN = 0xF81B; +const BUNDLE = 'AudioHapticDemo_'; +let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const driver = Driver.create(); + +async function runTest( + testCaseName: string, + buttonText: string, + delayTime: number, + description: string, + done: Function +) { + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, begin`); + + let btn = await driver.findComponent(ON.text(buttonText)); + if (btn !== undefined) { + await btn.click(); + await driver.delayMs(delayTime); + } else { + hilog.warn(DOMAIN, TAG, BUNDLE + `Button "${buttonText}" not found.`); + await driver.delayMs(delayTime); + } + + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, end`); + done(); +} + +export default function AudioHapticDemoTest() { + describe('AudioHapticDemoTest', () => { + beforeAll(() => { + abilityDelegator.startAbility({ + bundleName: 'com.example.myapplication', + abilityName: 'EntryAbility' + }); + }); + + beforeEach(() => {}) + afterEach(() => {}) + afterAll(() => {}) + + /** + * @tc.number AudioHapticDemo_NavigateToHapticPage_0001 + * @tc.name AudioHapticDemo_NavigateToHapticPage_0001 + * @tc.desc Navigate to the Haptic page via button click + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioHapticDemo_NavigateToHapticPage_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioHapticDemo_NavigateToHapticPage_0001', + '音振协同播放页面', + 2000, + 'Navigate to Haptic page (pages/Haptic) by clicking the corresponding button', + done + ); + }); + + /** + * @tc.number AudioHapticDemo_RegisterSource_0001 + * @tc.name AudioHapticDemo_RegisterSource_0001 + * @tc.desc Register audio and haptic resources using URI-based registration method + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioHapticDemo_RegisterSource_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioHapticDemo_RegisterSource_0001', + '使用注册源注册', + 3000, + 'Register audio-haptic source via registerSource API', + done + ); + }); + + /** + * @tc.number AudioHapticDemo_RegisterFromFd_0001 + * @tc.name AudioHapticDemo_RegisterFromFd_0001 + * @tc.desc Register audio and haptic resources using file descriptor from rawfile + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioHapticDemo_RegisterFromFd_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioHapticDemo_RegisterFromFd_0001', + '使用描述符注册', + 4000, + 'Register audio-haptic source via registerSourceFromFd using rawfile descriptors', + done + ); + }); + + /** + * @tc.number AudioHapticDemo_StartPlayback_0001 + * @tc.name AudioHapticDemo_StartPlayback_0001 + * @tc.desc Start synchronized audio and haptic playback + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioHapticDemo_StartPlayback_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioHapticDemo_StartPlayback_0001', + '音振协同播放开始', + 2000, + 'Start synchronized audio and haptic playback', + done + ); + }); + + /** + * @tc.number AudioHapticDemo_StopPlayback_0001 + * @tc.name AudioHapticDemo_StopPlayback_0001 + * @tc.desc Stop ongoing audio and haptic playback + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioHapticDemo_StopPlayback_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioHapticDemo_StopPlayback_0001', + '音振协同播放停止', + 2000, + 'Stop ongoing synchronized audio and haptic playback', + done + ); + }); + + /** + * @tc.number AudioHapticDemo_ReleaseResources_0001 + * @tc.name AudioHapticDemo_ReleaseResources_0001 + * @tc.desc Release audio haptic player and unregister source + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioHapticDemo_ReleaseResources_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioHapticDemo_ReleaseResources_0001', + '音振协同播放释放', + 3000, + 'Release audio haptic player and unregister the registered source', + done + ); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/ets/test/renderer.test.ets b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/ets/test/renderer.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..ebd53ae6860fcdc30761a87dae6018da492169ee --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/ets/test/renderer.test.ets @@ -0,0 +1,332 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, Level } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const TAG = '[AudioRendererDemoTest]'; +const DOMAIN = 0xF81C; +const BUNDLE = 'AudioRendererDemo_'; +let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const driver = Driver.create(); + +async function runTest( + testCaseName: string, + buttonText: string, + delayTime: number, + description: string, + done: Function +) { + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, begin`); + + let btn = await driver.findComponent(ON.text(buttonText)); + if (btn !== undefined) { + await btn.click(); + await driver.delayMs(delayTime); + } else { + hilog.warn(DOMAIN, TAG, BUNDLE + `Button "${buttonText}" not found.`); + await driver.delayMs(delayTime); + } + + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, end`); + done(); +} + +export default function AudioRendererDemoTest() { + describe('AudioRendererDemoTest', () => { + beforeAll(() => { + abilityDelegator.startAbility({ + bundleName: 'com.example.myapplication', + abilityName: 'EntryAbility' + }); + }); + + beforeEach(() => {}) + afterEach(() => {}) + afterAll(() => {}) + + /** + * @tc.number AudioRendererDemo_NavigateToRendererPage_0001 + * @tc.name AudioRendererDemo_NavigateToRendererPage_0001 + * @tc.desc Navigate to the Renderer page via button click + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_NavigateToRendererPage_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_NavigateToRendererPage_0001', + '音频播放页面', + 2000, + 'Navigate to Renderer page (pages/Renderer) by clicking the corresponding button', + done + ); + }); + + /** + * @tc.number AudioRendererDemo_Init_0001 + * @tc.name AudioRendererDemo_Init_0001 + * @tc.desc Initialize AudioRenderer instance and set callbacks + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_Init_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_Init_0001', + '初始化', + 2000, + 'Initialize AudioRenderer and register callbacks', + done + ); + }); + + /** + * @tc.number AudioRendererDemo_Start_0001 + * @tc.name AudioRendererDemo_Start_0001 + * @tc.desc Start audio rendering + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_Start_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_Start_0001', + '开始播放', + 2000, + 'Start audio rendering playback', + done + ); + }); + + /** + * @tc.number AudioRendererDemo_Pause_0001 + * @tc.name AudioRendererDemo_Pause_0001 + * @tc.desc Pause ongoing audio rendering + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_Pause_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_Pause_0001', + '暂停播放', + 2000, + 'Pause ongoing audio rendering', + done + ); + }); + + /** + * @tc.number AudioRendererDemo_Stop_0001 + * @tc.name AudioRendererDemo_Stop_0001 + * @tc.desc Stop audio rendering + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_Stop_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_Stop_0001', + '停止播放', + 2000, + 'Stop audio rendering', + done + ); + }); + + /** + * @tc.number AudioRendererDemo_Release_0001 + * @tc.name AudioRendererDemo_Release_0001 + * @tc.desc Release AudioRenderer resources + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_Release_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_Release_0001', + '释放资源', + 3000, + 'Release AudioRenderer and unregister listeners', + done + ); + }); + + /** + * @tc.number AudioRendererDemo_GetVolume_0001 + * @tc.name AudioRendererDemo_GetVolume_0001 + * @tc.desc Get system volume info for music stream + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_GetVolume_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_GetVolume_0001', + '获取音频流音量', + 1500, + 'Get min/max/current volume of music stream', + done + ); + }); + + /** + * @tc.number AudioRendererDemo_SetAppVolume_0001 + * @tc.name AudioRendererDemo_SetAppVolume_0001 + * @tc.desc Set and query application volume percentage + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_SetAppVolume_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_SetAppVolume_0001', + '设置应用的音量', + 2500, + 'Set app volume to 20% and verify', + done + ); + }); + + /** + * @tc.number AudioRendererDemo_SetStreamVolume_0001 + * @tc.name AudioRendererDemo_SetStreamVolume_0001 + * @tc.desc Set and get renderer stream volume + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_SetStreamVolume_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_SetStreamVolume_0001', + '设置音频流音量', + 2000, + 'Set renderer volume to 0.5 and retrieve it', + done + ); + }); + + /** + * @tc.number AudioRendererDemo_GetDevices_0001 + * @tc.name AudioRendererDemo_GetDevices_0001 + * @tc.desc Get output audio devices info + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_GetDevices_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_GetDevices_0001', + '是否支持空间音频', + 1500, + 'Query output audio devices via RoutingManager', + done + ); + }); + + /** + * @tc.number AudioRendererDemo_GetSpatialStatus_0001 + * @tc.name AudioRendererDemo_GetSpatialStatus_0001 + * @tc.desc Check if spatial audio is enabled on current device + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_GetSpatialStatus_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_GetSpatialStatus_0001', + '查询空间音频开关状态', + 1500, + 'Check spatialization enabled status', + done + ); + }); + + /** + * @tc.number AudioRendererDemo_SubscribeSpatial_0001 + * @tc.name AudioRendererDemo_SubscribeSpatial_0001 + * @tc.desc Subscribe to spatial audio enable change event + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_SubscribeSpatial_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_SubscribeSpatial_0001', + '订阅开关状态事件', + 1500, + 'Register spatializationEnabledChangeForCurrentDevice callback', + done + ); + }); + + /** + * @tc.number AudioRendererDemo_UnsubscribeSpatial_0001 + * @tc.name AudioRendererDemo_UnsubscribeSpatial_0001 + * @tc.desc Unsubscribe from spatial audio event + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_UnsubscribeSpatial_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_UnsubscribeSpatial_0001', + '取消订阅', + 1000, + 'Unregister spatializationEnabledChangeForCurrentDevice callback', + done + ); + }); + + /** + * @tc.number AudioRendererDemo_GetRendererState_0001 + * @tc.name AudioRendererDemo_GetRendererState_0001 + * @tc.desc Get current AudioRenderer state + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_GetRendererState_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_GetRendererState_0001', + '查看播放的状态', + 1000, + 'Retrieve and log current AudioRenderer state', + done + ); + }); + + /** + * @tc.number AudioRendererDemo_GetAllStreams_0001 + * @tc.name AudioRendererDemo_GetAllStreams_0001 + * @tc.desc Get all active audio renderer stream info + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererDemo_GetAllStreams_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererDemo_GetAllStreams_0001', + '获取所有流信息', + 2500, + 'Fetch all current audio renderer info via StreamManager', + done + ); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/module.json5 b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..0a099ee4bd05a001f943915cd4eb2e01aabeaca0 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/ohosTest/module.json5 @@ -0,0 +1,25 @@ +/* +* 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_test", + "type": "feature", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/test/List.test.ets b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..c2359615fdfb661f743a2cac5886892209c7aee6 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/test/List.test.ets @@ -0,0 +1,19 @@ +/* +* 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 localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/test/LocalUnit.test.ets b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..45ca114e698503447e7ccc80364c54cf7fa50f46 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,47 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/hvigor/hvigor-config.json5 b/MediaKit/AudioKit/AudioRendererSampleJS/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7d7d5f8aa0d0c54d1c8daf78625b9b3bbf52aee8 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/hvigor/hvigor-config.json5 @@ -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. +*/ +{ + "modelVersion": "6.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | 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 */ + // "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */ + }, + "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/MediaKit/AudioKit/AudioRendererSampleJS/hvigorfile.ts b/MediaKit/AudioKit/AudioRendererSampleJS/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c5b9939f0c562551a58ebd26abae33caf4f5e28 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/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. */ +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/oh-package.json5 b/MediaKit/AudioKit/AudioRendererSampleJS/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..d4c67d9bed2040005240f4f66a6601227e30809b --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/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": "6.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.24", + "@ohos/hamock": "1.0.0" + } +} diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/ohosTest.md b/MediaKit/AudioKit/AudioRendererSampleJS/ohosTest.md new file mode 100644 index 0000000000000000000000000000000000000000..3d9e24711a019603c27c9f5c06e74dcf32c994e7 --- /dev/null +++ b/MediaKit/AudioKit/AudioRendererSampleJS/ohosTest.md @@ -0,0 +1,28 @@ +# 音频播放测试用例归档 + +## 用例表 + +| 测试功能 | 预置条件 | 输入 | 预期输出 | 是否自动 | +|----------------------------------|--------------|------------------------|----------------------------------------|---------| +| 拉起应用 | 设备正常运行 | - | 成功拉起应用,进入首页 | 是 | +| 进入音振协同播放页面 | 位于主页 | 点击"音振协同播放页面" | 进入 Haptic 演示页面 | 是 | +| 使用注册源注册音振资源 | 音振页面 | 点击"使用注册源注册" | 通过 URI 注册音频与触觉资源成功 | 是 | +| 使用描述符注册音振资源 | 音振页面 | 点击"使用描述符注册" | 通过 rawfile 文件描述符注册资源成功 | 是 | +| 开始音振协同播放 | 已注册音振资源 | 点击"音振协同播放开始" | 启动同步的音频与触觉播放 | 是 | +| 停止音振协同播放 | 播放进行中 | 点击"音振协同播放停止" | 停止音振协同播放 | 是 | +| 释放音振协同资源 | 已注册音振资源 | 点击"音振协同播放释放" | 释放播放器并注销已注册的音振源 | 是 | +| 进入音频播放页面 | 位于主页 | 点击"音频播放页面" | 进入 AudioRenderer 演示页面 | 是 | +| 初始化 AudioRenderer | 音频播放页面 | 点击"初始化" | 创建 AudioRenderer 实例并注册回调 | 是 | +| 开始播放 | 已初始化渲染器 | 点击"开始播放" | 启动音频渲染播放 | 是 | +| 暂停播放 | 播放进行中 | 点击"暂停播放" | 暂停当前音频渲染 | 是 | +| 停止播放 | 播放进行中或已暂停 | 点击"停止播放" | 停止音频渲染 | 是 | +| 释放 AudioRenderer 资源 | 已初始化渲染器 | 点击"释放资源" | 释放渲染器并注销监听器 | 是 | +| 获取音频流音量 | 已初始化渲染器 | 点击"获取音频流音量" | 获取音乐流的最小/最大/当前音量值 | 是 | +| 设置应用的音量 | 已初始化渲染器 | 点击"设置应用的音量" | 将应用音量设为 20% 并验证生效 | 是 | +| 设置音频流音量 | 已初始化渲染器 | 点击"设置音频流音量" | 设置渲染器流音量为 0.5 并读回验证 | 是 | +| 查询是否支持空间音频 | 已初始化渲染器 | 点击"是否支持空间音频" | 查询当前输出设备是否支持空间音频 | 是 | +| 查询空间音频开关状态 | 已初始化渲染器 | 点击"查询空间音频开关状态" | 获取空间音频功能当前启用状态 | 是 | +| 订阅空间音频开关状态事件 | 已初始化渲染器 | 点击"订阅开关状态事件" | 注册空间音频启用状态变更回调 | 是 | +| 取消订阅空间音频开关状态事件 | 已初始化渲染器 | 点击"取消订阅" | 移除空间音频状态变更回调 | 是 | +| 查看播放的状态 | 已初始化渲染器 | 点击"查看播放的状态" | 获取并记录当前 AudioRenderer 的状态(如 playing/paused) | 是 | +| 获取所有流信息 | 已初始化渲染器 | 点击"获取所有流信息" | 通过 StreamManager 获取所有活跃音频流信息 | 是 | \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/screenshots/haptic.png b/MediaKit/AudioKit/AudioRendererSampleJS/screenshots/haptic.png new file mode 100644 index 0000000000000000000000000000000000000000..6b3d8202e96d4da2a1eeea149b53ead776fe8826 Binary files /dev/null and b/MediaKit/AudioKit/AudioRendererSampleJS/screenshots/haptic.png differ diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/screenshots/index.png b/MediaKit/AudioKit/AudioRendererSampleJS/screenshots/index.png new file mode 100644 index 0000000000000000000000000000000000000000..3564465092cb42aa116364ac3f36593ddd1b8dce Binary files /dev/null and b/MediaKit/AudioKit/AudioRendererSampleJS/screenshots/index.png differ diff --git a/MediaKit/AudioKit/AudioRendererSampleJS/screenshots/renderer.png b/MediaKit/AudioKit/AudioRendererSampleJS/screenshots/renderer.png new file mode 100644 index 0000000000000000000000000000000000000000..b7a47e95f81f90284b996a2dd6f97efd89027ddc Binary files /dev/null and b/MediaKit/AudioKit/AudioRendererSampleJS/screenshots/renderer.png differ diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/.gitignore b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/.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/MediaKit/AudioKit/AudioRoutingManagerSampleJS/AppScope/app.json5 b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..02e14fba2e646d86166447488188b104168fecbc --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/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.myapplication", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:layered_image", + "label": "$string:app_name" + } +} diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/AppScope/resources/base/element/string.json b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..1080233f01384411ec684b58955cb8808746fdd3 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "MyApplication" + } + ] +} diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/AppScope/resources/base/media/background.png b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/AppScope/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/AppScope/resources/base/media/background.png differ diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/AppScope/resources/base/media/foreground.png b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/AppScope/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9427585b36d14b12477435b6419d1f07b3e0bb Binary files /dev/null and b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/AppScope/resources/base/media/foreground.png differ diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/AppScope/resources/base/media/layered_image.json b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/AppScope/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/AppScope/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/MediaKit/AudioKit/AudioRoutingManagerSampleJS/README.md b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/README.md new file mode 100644 index 0000000000000000000000000000000000000000..8bd556ef5683141736226e89eff38b0fa2491bb0 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/README.md @@ -0,0 +1,122 @@ +# 音频设备路由管理功能示例 + +## 介绍 + +本示例基于AudioRender能力,实现了查询和监听音频输入设备、查询和监听音频输出设备、实现音频输入设备路由切换、实现音频输出设备路由切换、响应输出设备变更时合理暂停等功能,包含了功能调用接口的完整链路。 + +## 效果图预览 + +**图1**:首页 + +选择跳转到对应功能页面。 + + + + +**图2**:查询和监听音频输入设备页 + +点击'获取输入设备列表'按钮,即可进行设备连接状态监听、获取当前所有输入设备信息以及注销设备连接状态监听操作。获取到的输入设备信息在日志信息下方打印。 + + + +**图3**:查询和监听音频输出设备页 + +- 点击'获取输出设备列表'按钮,即可进行设备连接状态监听、获取当前所有输出设备信息以及注销设备连接状态监听等操作。获取到的输出设备信息在日志信息下方打印。 +- 点击'获取优先输出设备'按钮,即可进行高优先级输出设备变化监听、获取当前最高优先级输出设备以及注销高优先级输出设备变化监听操作。获取到的最高优先级输出设备信息在日志信息下方打印。 + + + +**图4**:实现音频输出设备路由切换页 + +- 点击'设置蓝牙/近场优选低时延'按钮,即可在蓝牙或星闪设备连接时优先使用其作为输入设备进行录制。 +- 点击'获取并选择输入设备'按钮,即可设置当前输入设备。实际设置的输入设备取决于外设连接情况。 +- 点击'查询已选输入设备'按钮,即可查询选择输入设备是否成功。获取到的信息在日志信息下方打印。 +- 点击'清空已选输入设备'按钮,即可将输入设备设置回默认设备。 + + + +**图5**:查询和监听音频输入设备页 + +- 点击'设置为扬声器'按钮,即可设置当前输出设备为扬声器。 +- 点击'设置为默认输出'按钮,即可设置当前输出设备为默认输出设备。 +- 点击'获取当前默认输出'按钮,即可查询当前输出设备信息。获取到的信息在日志信息下方打印。 + + + +**图6**:查询和监听音频输入设备页 + +- 点击'创建音频播放实例'按钮,会进行音频流输出设备变化及原因监听、创建播放控制实例以及初始化播放流等动作。 +- 点击'设置为本机扬声器'按钮,即可设置默认输出设备为本机扬声器。 +- 点击'设置为系统默认设备'按钮,即可设置默认输出设备为系统默认设备。 + + + +## 工程结构&模块类型 + +``` +├───entry/src/main/ets +│ ├───pages +│ │ └───Index.ets // 首页。 +│ │ └───FindAndListenAudioInputDevice.ets // 查询和监听音频输入设备页。 +│ │ └───FindAndListenAudioOutputDevice.ets // 查询和监听音频输出设备页面。 +│ │ └───InputDeviceRoutingSwitching.ets // 实现音频输入设备路由切换页面。 +│ │ └───ListenDeviceByAudioSession.ets // 通过AudioSession查询和监听音频输出设备页。 +│ │ └───OutputDeviceChangePause.ets // 响应输出设备变更时合理暂停和实现音频输出设备路由切换页。 +└───entry/src/main/resources // 资源目录。 +``` +### 具体实现 + +### 查询和监听音频输入设备 +- 源码参考:[FindAndListenAudioInputDevice.ets](entry/src/main/ets/pages/FindAndListenAudioInputDevice.ets) +- 使用流程: + 点击'获取输出设备列表'按钮,具体处理流程如下。 + - 调用`audioRoutingManager.on`监听设备连接状态,对设备接入与断开事件进行响应。 + - 调用`audioRoutingManager.getDevices`获取当前所有输入设备信息。 + - 调用`audioRoutingManager.off`注销设备连接状态监听。 + +### 查询和监听音频输出设备 +- 查询和监听音频输出设备:[FindAndListenAudioOutputDevice.ets](entry/src/main/ets/pages/FindAndListenAudioOutputDevice.ets) +- 使用流程: + - 点击'获取输出设备列表'按钮,先调用`audioRoutingManager.on`监听设备连接状态,对设备接入与断开事件进行响应。然后调用`audioRoutingManager.getDevices`获取当前所有输出设备信息。最后调用`audioRoutingManager.off`注销设备连接状态监听。 + - 点击'获取优先输出设备'按钮,先配置`audio.AudioRendererInfo`信息,包括流类型与渲染器标志。接着调用`audioRoutingManager.on`监听高优先级输出设备连接状态,对设备接入与断开事件进行响应。然后调用`audioRoutingManager.getPreferOutputDeviceForRendererInfo`获取当前最高优先级的输出设备。最后调用`audioRoutingManager.off`注销高优先级输出设备连接状态监听。 + +### 通过AudioSession查询和监听输出设备 +- 源码参考:[ListenDeviceByAudioSession.ets](entry/src/main/ets/pages/ListenDeviceByAudioSession.ets) +- 使用流程: + - 点击'设置为扬声器'按钮,调用`audioRenderer.setDefaultOutputDevice`默认输出设备为本机扬声器。 + - 点击'设置为默认输出'按钮,调用`audioRenderer.setDefaultOutputDevice`设置默认输出设备为系统默认输出设备,即取消应用设置的默认设备,交由系统选择设备。 + - 点击'获取当前默认输出'按钮,调用`audioSessionManager.getDefaultOutputDevice`获取默认输出设备类型,获取到的信息在日志信息栏下方打印。 + + +### 实现音频输入设备路由切换* +- 源码参考:[InputDeviceRoutingSwitching.ets](entry/src/main/ets/pages/InputDeviceRoutingSwitching.ets) +- 使用流程: + - 点击'设置蓝牙/近场优选低时延'按钮,调用`audioSessionManager.setBluetoothAndNearlinkPreferredRecordCategory`设置蓝牙/星闪设备连接时为优先输入设备。 + - 点击'获取并选择输入设备'按钮,首先调用`audioSessionManager.on`对输入设备连接状态变化以及输入设备变更两个事件进行监听。接着调用`audioSessionManager.getAvailableDevices`获取当前可选的音频输入设备列表,最后调用`audioSessionManager.selectMediaInputDevice`将获取到的输入设备列表中的第一个设置为输入设备。 + - 点击'查询已选输入设备'按钮,调用`audioSessionManager.getSelectedMediaInputDevice`获取已选输入设备信息,获取到的信息在日志信息栏下方打印。 + - 点击'清空已选输入设备'按钮,调用`audioSessionManager.clearSelectedMediaInputDevice`清空通过`selectMediaInputDevice`选择的输入设备,输入设备返回为系统默认设备。 + +### 实现音频输出设备路由切换与合理暂停* +- 源码参考:[OutputDeviceChangePause.ets](entry/src/main/ets/pages/OutputDeviceChangePause.ets) +- 使用流程: + - 点击'创建音频播放实例'按钮,首先调用`audio.createAudioRenderer`创建`AudioRenderer`实例,然后配置监听相应音频流输出设备变化及原因,回调内容在输出设备变化回调栏下方打印。 + - 点击'设置为本机扬声器'按钮,调用`audioSessionManager.setDefaultOutputDevice`设置默认输出设备为本机扬声器。 + - 点击'设置为系统默认设备'按钮,调用`audioSessionManager.setDefaultOutputDevice`设置默认输出设备为系统默认输出设备,即取消应用设置的默认设备,交由系统选择设备。 + +## 相关权限 + +不涉及。 + +## 模块依赖 + +不涉及。 + +## 约束与限制 + +1. 本示例支持在标准系统上运行,支持设备:RK3568。 + +2. 本示例支持API version 20,版本号: 6.0.0.43。 + +3. 本示例已支持使Build Version: 6.0.0.43, built on August 24, 2025。 + +4. 高等级APL特殊签名说明:无。 \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/build-profile.json5 b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..3b9a7bcc04b86b38badd941e5a1109732f2ce493 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/build-profile.json5 @@ -0,0 +1,56 @@ +/* +* 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": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "targetSdkVersion": "6.0.0(20)", + "compatibleSdkVersion": "6.0.0(20)", + "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/MediaKit/AudioKit/AudioRoutingManagerSampleJS/code-linter.json5 b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/code-linter.json5 new file mode 100644 index 0000000000000000000000000000000000000000..62baae473144070aa0fc532fce773652507047a1 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/code-linter.json5 @@ -0,0 +1,46 @@ +/* +* 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": { + "@security/no-unsafe-aes": "error", + "@security/no-unsafe-hash": "error", + "@security/no-unsafe-mac": "warn", + "@security/no-unsafe-dh": "error", + "@security/no-unsafe-dsa": "error", + "@security/no-unsafe-ecdsa": "error", + "@security/no-unsafe-rsa-encrypt": "error", + "@security/no-unsafe-rsa-sign": "error", + "@security/no-unsafe-rsa-key": "error", + "@security/no-unsafe-dsa-key": "error", + "@security/no-unsafe-dh-key": "error", + "@security/no-unsafe-3des": "error" + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/.gitignore b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/build-profile.json5 b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..2ded9f2f9d44b4d00eb8dbe3fccae05dde77acb5 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/build-profile.json5 @@ -0,0 +1,47 @@ +/* +* 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": { + "resOptions": { + "copyCodeResource": { + "enable": false + } + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/hvigorfile.ts b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..61cca58be096424d17b6aadf3dff1c2ea06e05f3 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/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. */ +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/obfuscation-rules.txt b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/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/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/oh-package-lock.json5 b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/oh-package-lock.json5 new file mode 100644 index 0000000000000000000000000000000000000000..337d78154847af4e2cbb2dc0ee0660d04ff85983 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/oh-package-lock.json5 @@ -0,0 +1,28 @@ +{ + "meta": { + "stableOrder": true, + "enableUnifiedLockfile": false + }, + "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.15": "@ohos/hypium@1.0.15" + }, + "packages": { + "@ohos/hamock@1.0.0": { + "name": "@ohos/hamock", + "version": "1.0.0", + "integrity": "sha512-K6lDPYc6VkKe6ZBNQa9aoG+ZZMiwqfcR/7yAVFSUGIuOAhPvCJAo9+t1fZnpe0dBRBPxj2bxPPbKh69VuyAtDg==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hamock/-/hamock-1.0.0.har", + "registryType": "ohpm" + }, + "@ohos/hypium@1.0.15": { + "name": "@ohos/hypium", + "version": "1.0.15", + "integrity": "sha512-AhkuYX2l/IzrVARV/hKRGsJDQPtZ5bygr6Q1N2M9W15kBllNYL3khQ0dNvJwh/CIGoEPMDVcME9q6MhFGccqkw==", + "resolved": "https://ohpm.openharmony.cn/ohpm/@ohos/hypium/-/hypium-1.0.15.har", + "registryType": "ohpm" + } + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/oh-package.json5 b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..e3d2e18789ceb3234d777badfa2f7da53be0e238 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/oh-package.json5 @@ -0,0 +1,27 @@ +/* +* 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": { + "@ohos/hypium": "1.0.15", + "@ohos/hamock": "1.0.0" + } +} + diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/entryability/EntryAbility.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..4ae1807cdc1b76c6ef0123c184f575f74b21b8f6 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,62 @@ +/* +* 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 { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +const DOMAIN = 0x0000; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + try { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + } catch (err) { + hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err)); + } + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); + return; + } + hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..96164e538b152554e43e37a07d8ff4e7db326a0d --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,30 @@ +/* +* 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'; + +const DOMAIN = 0x0000; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(DOMAIN, 'testTag', 'onBackup ok'); + await Promise.resolve(); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + await Promise.resolve(); + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/FindAndListenAudioInputDevice.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/FindAndListenAudioInputDevice.ets new file mode 100644 index 0000000000000000000000000000000000000000..b11261c061078cb149fa80112c8d86a37c86e55a --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/FindAndListenAudioInputDevice.ets @@ -0,0 +1,223 @@ +/* +* 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. +*/ + +// [Start getRoutingManager_input] +// [Start getDevices_input] +// [Start listen_InputStatus] +import { audio } from '@kit.AudioKit'; // 导入audio模块。 +// [StartExclude getDevices_input] +// [StartExclude listen_InputStatus] +let audioManager = audio.getAudioManager(); // 需要先创建AudioManager实例。 +let audioRoutingManager = audioManager.getRoutingManager(); // 再调用AudioManager的方法创建AudioRoutingManager实例。 +// [End getRoutingManager_input] + +// 全局UI更新回调 +let globalLogUpdate: ((msg: string, isError: boolean) => void) | undefined; +let globalCallbackUpdate: ((msg: string) => void) | undefined; + +function listenInputStatus() { + // [EndExclude listen_InputStatus] + // 监听音频设备状态变化。 + audioRoutingManager.on('deviceChange', audio.DeviceFlag.INPUT_DEVICES_FLAG, + (deviceChanged: audio.DeviceChangeAction) => { + console.info('device change type : ' + deviceChanged.type); // 设备连接状态变化,0为连接,1为断开连接。 + console.info('device descriptor size : ' + deviceChanged.deviceDescriptors.length); + console.info('device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceRole); // 设备角色。 + console.info('device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceType); // 设备类型。 + + // [StartExclude listen_InputStatus] + // 为UI收集信息 + let callbackMsg = ''; + callbackMsg += 'device change type : ' + deviceChanged.type + '\n'; + callbackMsg += 'device descriptor size : ' + deviceChanged.deviceDescriptors.length + '\n'; + callbackMsg += 'device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceRole + '\n'; + callbackMsg += 'device change descriptor : ' + deviceChanged.deviceDescriptors[0].deviceType; + + if (globalCallbackUpdate) { + globalCallbackUpdate(callbackMsg); + } + }); +} + +function cancelListenInputStatus() { + // [EndExclude listen_InputStatus] + // 取消监听音频设备状态变化。 + audioRoutingManager.off('deviceChange', (deviceChanged: audio.DeviceChangeAction) => { + console.info('Should be no callback.'); + }); + // [End listen_InputStatus] +} + +async function getDevices() { + // 获取前先监听 + listenInputStatus(); + + // [EndExclude getDevices_input] + audioRoutingManager.getDevices(audio.DeviceFlag.INPUT_DEVICES_FLAG).then((data: audio.AudioDeviceDescriptors) => { + console.info('Promise returned to indicate that the device list is obtained.'); + + // [StartExclude getDevices_input] + // 为UI收集设备信息 + let deviceInfo = 'Promise returned to indicate that the device list is obtained.\n'; + deviceInfo += 'Device count: ' + data.length + '\n'; + for (let i = 0; i < data.length; i++) { + console.info(`Device ${i} - id: ${data[i].id}`); + console.info(`Device ${i} - name: ${data[i].name}`); + console.info(`Device ${i} - type: ${data[i].deviceType}`); + console.info(`Device ${i} - role: ${data[i].deviceRole}`); + console.info(`Device ${i} - address: ${data[i].address}`); + + deviceInfo += `Device ${i} - id: ${data[i].id}\n`; + deviceInfo += `Device ${i} - name: ${data[i].name}\n`; + deviceInfo += `Device ${i} - type: ${data[i].deviceType}\n`; + deviceInfo += `Device ${i} - role: ${data[i].deviceRole}\n`; + deviceInfo += `Device ${i} - address: ${data[i].address}\n`; + } + if (globalLogUpdate) { + globalLogUpdate(deviceInfo, false); + } + // [EndExclude getDevices_input] + }); + // [End getDevices_input] + + // 获取后取消监听 + cancelListenInputStatus(); +} + +@Entry +@Component +struct Index { + @State currentState: string = '未初始化'; + @State logMessages: string = '暂无日志信息'; + @State callbackMessages: string = '暂无回调信息'; + + aboutToAppear(): void { + // 设置全局回调 + globalLogUpdate = (msg, isError) => this.updateLogInfo(msg, isError); + globalCallbackUpdate = (msg) => this.updateCallbackInfo(msg); + } + + // 更新日志信息 + updateLogInfo(msg: string, isError: boolean): void { + const timestamp = new Date().toLocaleTimeString(); + const prefix = isError ? '[ERROR]' : '[INFO]'; + this.logMessages = `[${timestamp}] ${prefix} ${msg}`; + } + + // 更新回调信息 + updateCallbackInfo(msg: string): void { + const timestamp = new Date().toLocaleTimeString(); + this.callbackMessages = `[${timestamp}] ${msg}`; + } + + build(): void { + Scroll() { + Column() { + // 信息显示区域 + Column() { + Text('实时状态信息') + .fontSize(18) + .fontWeight(600) + .margin({ bottom: 12 }) + + // 当前状态 + Column() { + Text('当前状态') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.currentState) + .fontSize(14) + .fontColor('#007DFF') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 日志信息 + Column() { + Text('日志信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.logMessages) + .fontSize(12) + .fontColor('#52C41A') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 回调信息 + Column() { + Text('回调信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.callbackMessages) + .fontSize(12) + .fontColor('#FA8C16') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 20 }) + } + .width('100%') + .padding(16) + .backgroundColor(Color.White) + .borderRadius(12) + .margin({ bottom: 16 }) + + // 功能按钮 + Column() { + Text('获取输入设备列表').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('90%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在获取输入设备列表...'; + await getDevices(); + this.currentState = '获取完成'; + }); + } + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5') + .padding(16); + } + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/FindAndListenAudioOutputDevice.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/FindAndListenAudioOutputDevice.ets new file mode 100644 index 0000000000000000000000000000000000000000..c9e2afd6b4d0f1e06e817506dc7ef2bfe7a63d3b --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/FindAndListenAudioOutputDevice.ets @@ -0,0 +1,328 @@ +/* +* 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. +*/ + +// [Start getRoutingManager_output] +// [Start get_OutputDevices] +// [Start deviceChange_output] +// [Start get_PreferOutputDeviceForRendererInfo] +// [Start listen_OutputDeviceChangeForRendererInfo] +import { audio } from '@kit.AudioKit'; // 导入audio模块。 +// [StartExclude get_OutputDevices] +// [StartExclude deviceChange_output] +// [StartExclude getRoutingManager_output] +// [StartExclude listen_OutputDeviceChangeForRendererInfo] +import { BusinessError } from '@kit.BasicServicesKit'; +// [EndExclude getRoutingManager_output] +// [StartExclude get_PreferOutputDeviceForRendererInfo] +let audioManager = audio.getAudioManager(); // 需要先创建AudioManager实例。 +let audioRoutingManager = audioManager.getRoutingManager(); // 再调用AudioManager的方法创建AudioRoutingManager实例。 +// [End getRoutingManager_output] +// [EndExclude get_PreferOutputDeviceForRendererInfo] +// [EndExclude listen_OutputDeviceChangeForRendererInfo] +let rendererInfo: audio.AudioRendererInfo = { + usage: audio.StreamUsage.STREAM_USAGE_MUSIC,// 音频流使用类型:音乐。根据业务场景配置,参考StreamUsage。 + rendererFlags: 0 // 音频渲染器标志。 +}; +// [StartExclude get_PreferOutputDeviceForRendererInfo] +// [StartExclude listen_OutputDeviceChangeForRendererInfo] + +// 全局UI更新回调 +let globalLogUpdate: ((msg: string, isError: boolean) => void) | undefined; +let globalCallbackUpdate: ((msg: string) => void) | undefined; +let globalPreferDeviceUpdate: ((msg: string) => void) | undefined; + +function listenOutputStatus() { + // [EndExclude deviceChange_output] + // 监听音频设备状态变化。 + audioRoutingManager.on('deviceChange', audio.DeviceFlag.OUTPUT_DEVICES_FLAG, (deviceChanged: audio.DeviceChangeAction) => { + console.info(`device change type : ${deviceChanged.type}`); // 设备连接状态变化,0为连接,1为断开连接。 + console.info(`device descriptor size : ${deviceChanged.deviceDescriptors.length}`); + console.info(`device change descriptor : ${deviceChanged.deviceDescriptors[0].deviceRole}`); // 设备角色。 + console.info(`device change descriptor : ${deviceChanged.deviceDescriptors[0].deviceType}`); // 设备类型。 + + // [StartExclude deviceChange_output] + // 为UI收集信息 + let callbackMsg = ''; + callbackMsg += `device change type : ${deviceChanged.type}\n`; + callbackMsg += `device descriptor size : ${deviceChanged.deviceDescriptors.length}\n`; + callbackMsg += `device change descriptor : ${deviceChanged.deviceDescriptors[0].deviceRole}\n`; + callbackMsg += `device change descriptor : ${deviceChanged.deviceDescriptors[0].deviceType}`; + + if (globalCallbackUpdate) { + globalCallbackUpdate(callbackMsg); + } + // [EndExclude deviceChange_output] + }); + // [StartExclude deviceChange_output] +} + +function cancelListenOutputStatus() { + // [EndExclude deviceChange_output] + // 取消监听音频设备状态变化。 + audioRoutingManager.off('deviceChange'); + // [End deviceChange_output] +} + +async function getOutputDevices() { + // 获取前先监听 + listenOutputStatus(); + // [EndExclude get_OutputDevices] + audioRoutingManager.getDevices(audio.DeviceFlag.OUTPUT_DEVICES_FLAG).then((data: audio.AudioDeviceDescriptors) => { + console.info('Promise returned to indicate that the device list is obtained.'); + // [StartExclude get_OutputDevices] + // 为UI收集设备信息 + let deviceInfo = 'Promise returned to indicate that the device list is obtained.\n'; + if (globalLogUpdate) { + globalLogUpdate(deviceInfo, false); + } + // [EndExclude get_OutputDevices] + }); + // [End get_OutputDevices] + + // 获取后取消监听 + cancelListenOutputStatus(); +} + +function listenOutputDeviceChangeForRendererInfo() { + // [EndExclude listen_OutputDeviceChangeForRendererInfo] + // 监听最高优先级输出设备变化。 + audioRoutingManager.on('preferOutputDeviceChangeForRendererInfo', rendererInfo, (desc: audio.AudioDeviceDescriptors) => { + console.info(`device change descriptor : ${desc[0].deviceRole}`); // 设备角色。 + console.info(`device change descriptor : ${desc[0].deviceType}`); // 设备类型。 + + // [StartExclude listen_OutputDeviceChangeForRendererInfo] + // 为UI收集信息 + let preferMsg = ''; + preferMsg += `device change descriptor : ${desc[0].deviceRole}\n`; + preferMsg += `device change descriptor : ${desc[0].deviceType}`; + + if (globalPreferDeviceUpdate) { + globalPreferDeviceUpdate(preferMsg); + } + // [EndExclude listen_OutputDeviceChangeForRendererInfo] + }); + // [StartExclude listen_OutputDeviceChangeForRendererInfo] +} + +function cancelListenOutputDeviceChangeForRendererInfo() { + // [EndExclude listen_OutputDeviceChangeForRendererInfo] + // 取消监听最高优先级输出设备变化。 + audioRoutingManager.off('preferOutputDeviceChangeForRendererInfo'); + // [End listen_OutputDeviceChangeForRendererInfo] +} + +// [EndExclude get_PreferOutputDeviceForRendererInfo] +async function getPreferOutputDeviceForRendererInfo() { + // [StartExclude get_PreferOutputDeviceForRendererInfo] + // 获取前先监听 + listenOutputDeviceChangeForRendererInfo(); + // [EndExclude get_PreferOutputDeviceForRendererInfo] + audioRoutingManager.getPreferOutputDeviceForRendererInfo(rendererInfo).then((desc: audio.AudioDeviceDescriptors) => { + console.info(`device descriptor: ${desc}`); + + // [StartExclude get_PreferOutputDeviceForRendererInfo] + // 为UI收集信息 + let preferInfo = `device descriptor: ${desc}\n`; + if (globalLogUpdate) { + globalLogUpdate(preferInfo, false); + } + // [EndExclude get_PreferOutputDeviceForRendererInfo] + }).catch((err: BusinessError) => { + console.error(`Result ERROR: ${err}`); + // [StartExclude get_PreferOutputDeviceForRendererInfo] + const errorMsg = `Result ERROR: ${err}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude get_PreferOutputDeviceForRendererInfo] + }); + // [StartExclude get_PreferOutputDeviceForRendererInfo] + // 获取后取消监听 + cancelListenOutputDeviceChangeForRendererInfo(); + // [EndExclude get_PreferOutputDeviceForRendererInfo] +} +// [End get_PreferOutputDeviceForRendererInfo] + +@Entry +@Component +struct Index { + @State currentState: string = '未初始化'; + @State logMessages: string = '暂无日志信息'; + @State callbackMessages: string = '暂无回调信息'; + @State preferDeviceMessages: string = '暂无优先设备信息'; + + aboutToAppear(): void { + // 设置全局回调 + globalLogUpdate = (msg, isError) => this.updateLogInfo(msg, isError); + globalCallbackUpdate = (msg) => this.updateCallbackInfo(msg); + globalPreferDeviceUpdate = (msg) => this.updatePreferDeviceInfo(msg); + } + + // 更新日志信息 + updateLogInfo(msg: string, isError: boolean): void { + const timestamp = new Date().toLocaleTimeString(); + const prefix = isError ? '[ERROR]' : '[INFO]'; + this.logMessages = `[${timestamp}] ${prefix} ${msg}`; + } + + // 更新回调信息 + updateCallbackInfo(msg: string): void { + const timestamp = new Date().toLocaleTimeString(); + this.callbackMessages = `[${timestamp}] ${msg}`; + } + + // 更新优先设备信息 + updatePreferDeviceInfo(msg: string): void { + const timestamp = new Date().toLocaleTimeString(); + this.preferDeviceMessages = `[${timestamp}] ${msg}`; + } + + build(): void { + Scroll() { + Column() { + // 信息显示区域 + Column() { + Text('实时状态信息') + .fontSize(18) + .fontWeight(600) + .margin({ bottom: 12 }) + + // 当前状态 + Column() { + Text('当前状态') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.currentState) + .fontSize(14) + .fontColor('#007DFF') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 日志信息 + Column() { + Text('日志信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.logMessages) + .fontSize(12) + .fontColor('#52C41A') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 设备变化回调信息 + Column() { + Text('设备变化回调') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.callbackMessages) + .fontSize(12) + .fontColor('#FA8C16') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 优先设备变化回调信息 + Column() { + Text('优先设备变化回调') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.preferDeviceMessages) + .fontSize(12) + .fontColor('#9254DE') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 20 }) + } + .width('100%') + .padding(16) + .backgroundColor(Color.White) + .borderRadius(12) + .margin({ bottom: 16 }) + + // 功能按钮 + Row() { + Column() { + Text('获取输出设备列表').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 12, bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在获取输出设备列表...'; + await getOutputDevices(); + this.currentState = '获取完成'; + }); + + Column() { + Text('获取优先输出设备').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('45%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在获取优先输出设备...'; + await getPreferOutputDeviceForRendererInfo(); + this.currentState = '获取完成'; + }); + } + } + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5') + .padding(16); + } + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/Index.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..8f2fc0e18ea6109f9f5d21f1ce5a4581519f02cc --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,57 @@ +/* +* 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 {Router} from '@ohos.arkui.UIContext'; +const router = new Router(); + +@Entry +@Component +struct Index { + build() { + Column() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center}) { + this.textComponent('Index'); + this.buttonComponent('查询和监听音频输入设备页面', 'pages/FindAndListenAudioInputDevice'); + this.buttonComponent('查询和监听音频输出设备页面', 'pages/FindAndListenAudioOutputDevice'); + this.buttonComponent('实现音频输入设备路由切换页面', 'pages/InputDeviceRoutingSwitching'); + this.buttonComponent('通过AudioSession查询和监听音频输出设备页面', 'pages/ListenDeviceByAudioSession'); + this.buttonComponent('响应输出设备变更时合理暂停和实现音频输出设备路由切换页面', 'pages/OutputDeviceChangePause'); + }.size({ width: '100%', height: '100%' }) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + } + + @Builder + textComponent(name: string) { + Text(name) + .textAlign(TextAlign.Center) + .margin(10) + .fontSize($r('app.float.index_text_font_size')) + .size({ width: '100%', height: $r('app.float.index_button_height_size') }) + } + + @Builder + buttonComponent(name: string, dstUri: string) { + Button(name) + .width('50%') + .margin(10) + .onClick((e: ClickEvent) => { + router.pushUrl({ url: dstUri }); + }) + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/InputDeviceRoutingSwitching.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/InputDeviceRoutingSwitching.ets new file mode 100644 index 0000000000000000000000000000000000000000..d0359b65fd4d798f0341b9e82fb93d771d0f56d3 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/InputDeviceRoutingSwitching.ets @@ -0,0 +1,422 @@ +/* +* 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. +*/ + +// [Start set_BluetoothAndNearlinkPreferredRecordCategory] +// [Start select_MediaInputDevice] +import { audio } from '@kit.AudioKit'; // 导入audio模块。 +import { BusinessError } from '@kit.BasicServicesKit'; + +let audioManager = audio.getAudioManager(); // 需要先创建AudioManager实例。 + +let audioSessionManager = audioManager.getSessionManager(); // 再调用AudioManager的方法创建AudioSessionManager实例. + +// [StartExclude set_BluetoothAndNearlinkPreferredRecordCategory] +// [StartExclude select_MediaInputDevice] +// 全局UI更新回调 +let globalLogUpdate: ((msg: string, isError: boolean) => void) | undefined; +let globalAvailableDeviceUpdate: ((msg: string) => void) | undefined; +let globalCurrentDeviceUpdate: ((msg: string) => void) | undefined; +// [EndExclude select_MediaInputDevice] +// 监听音频可选输入设备连接状态变化事件,当有输入设备上下线时会收到回调通知。 +let availableDeviceChangeCallback = (deviceChanged: audio.DeviceChangeAction) => { + let data: audio.AudioDeviceDescriptors = deviceChanged.deviceDescriptors; + console.info(`Succeeded in using on or off function, AudioDeviceDescriptors: ${data}.`); + // [StartExclude select_MediaInputDevice] + const logMessage = `Succeeded in using on or off function, AudioDeviceDescriptors: ${data}.`; + if (globalAvailableDeviceUpdate) { + globalAvailableDeviceUpdate(logMessage); + } + // [EndExclude select_MediaInputDevice] +}; + +// 监听当前输入设备变化事件,当选择输入设备成功后会触发该回调。 +let currentInputDeviceChangedCallback = (currentInputDeviceChangedEvent: audio.CurrentInputDeviceChangedEvent) => { + console.info(`Succeeded in using on or off function, CurrentInputDeviceChangedEvent: + ${currentInputDeviceChangedEvent}.`); + // [StartExclude select_MediaInputDevice] + const logMessage = `Succeeded in using on or off function, CurrentInputDeviceChangedEvent: + ${currentInputDeviceChangedEvent}.`; + if (globalCurrentDeviceUpdate) { + globalCurrentDeviceUpdate(logMessage); + } + // [EndExclude select_MediaInputDevice] +}; + +// [StartExclude select_MediaInputDevice] +async function setBluetoothAndNearlinkPreferredRecordCategory() { + // [EndExclude set_BluetoothAndNearlinkPreferredRecordCategory] + audioSessionManager.setBluetoothAndNearlinkPreferredRecordCategory(audio.BluetoothAndNearlinkPreferredRecordCategory + .PREFERRED_LOW_LATENCY).then(() => { + console.info('Succeeded in setting bluetooth and nearlink preferred record category.'); + // [StartExclude set_BluetoothAndNearlinkPreferredRecordCategory] + const successMsg = 'Succeeded in setting bluetooth and nearlink preferred record category.'; + if (globalLogUpdate) { + globalLogUpdate(successMsg, false); + } + // [EndExclude set_BluetoothAndNearlinkPreferredRecordCategory] + }).catch((err: BusinessError) => { + console.error(`Failed to set bluetooth and nearlink preferred record category. Code: ${err.code}, + message: ${err.message}`); + // [StartExclude set_BluetoothAndNearlinkPreferredRecordCategory] + const errorMsg = `Failed to set bluetooth and nearlink preferred record category. Code: ${err.code}, + message: ${err.message}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude set_BluetoothAndNearlinkPreferredRecordCategory] + }); + // [End set_BluetoothAndNearlinkPreferredRecordCategory] +} + +function listenAvailableInputDeviceChange() { + // 监听音频可选输入设备连接状态变化事件 + // [EndExclude select_MediaInputDevice] + audioSessionManager.on('availableDeviceChange', audio.DeviceUsage.MEDIA_INPUT_DEVICES, availableDeviceChangeCallback); + // [StartExclude select_MediaInputDevice] +} + +function listenCurrentInputDeviceChanged() { + // 监听当前输入设备变化事件 + // [EndExclude select_MediaInputDevice] + audioSessionManager.on('currentInputDeviceChanged', currentInputDeviceChangedCallback); + // [StartExclude select_MediaInputDevice] + console.info('Started listening to current input device changes.'); +} + +function cancelListenAvailableInputDeviceChange() { + // [EndExclude select_MediaInputDevice] + // 取消监听音频可选输入设备连接状态变化事件 + audioSessionManager.off('availableDeviceChange', availableDeviceChangeCallback); + // [StartExclude select_MediaInputDevice] + console.info('Cancelled listening to available input device changes.'); +} + +function cancelListenCurrentInputDeviceChanged() { + // [EndExclude select_MediaInputDevice] + // 取消监听当前输入设备变化事件 + audioSessionManager.off('currentInputDeviceChanged', currentInputDeviceChangedCallback); + // [StartExclude select_MediaInputDevice] + console.info('Cancelled listening to current input device changes.'); +} + +async function getAndSelectInputDevice() { + // 获取前先监听 + listenAvailableInputDeviceChange(); + listenCurrentInputDeviceChanged(); + + // [EndExclude select_MediaInputDevice] + try { + // 获取当前可选的音频输入设备列表。 + let data: audio.AudioDeviceDescriptors = audioSessionManager.getAvailableDevices(audio.DeviceUsage.MEDIA_INPUT_DEVICES); + console.info(`Succeeded in getting available devices, AudioDeviceDescriptors: ${data}.`); + + // [StartExclude select_MediaInputDevice] + let deviceInfo = `Succeeded in getting available devices, AudioDeviceDescriptors: ${data}.\n`; + // [EndExclude select_MediaInputDevice] + + // 当前可选音频输入设备列表不为空时,可进行选择。 + if (data[0]) { + // 选择输入设备。 + await audioSessionManager.selectMediaInputDevice(data[0]).then(() => { + console.info('Succeeded in selecting media input device.'); + // [StartExclude select_MediaInputDevice] + const successMsg = 'Succeeded in selecting media input device.'; + deviceInfo += successMsg; + if (globalLogUpdate) { + globalLogUpdate(deviceInfo, false); + } + // [EndExclude select_MediaInputDevice] + }).catch((err: BusinessError) => { + console.error(`Failed to select media input device. Code: ${err.code}, message: ${err.message}`); + // [StartExclude select_MediaInputDevice] + const errorMsg = `Failed to select media input device. Code: ${err.code}, message: ${err.message}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude select_MediaInputDevice] + }); + } + } catch (err) { + let error = err as BusinessError; + console.error(`Failed to select media input device. Code: ${err.code}, message: ${err.message}`); + // [StartExclude select_MediaInputDevice] + const errorMsg = `Failed to select media input device. Code: ${err.code}, message: ${err.message}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude select_MediaInputDevice] + } + // [StartExclude select_MediaInputDevice] + // 获取后取消监听 + cancelListenAvailableInputDeviceChange(); + cancelListenCurrentInputDeviceChanged(); +} + +async function getSelectedInputDevice() { + // [EndExclude select_MediaInputDevice] + // 可通过该接口查询选择输入设备是否成功。 + try { + let device: audio.AudioDeviceDescriptor = audioSessionManager.getSelectedMediaInputDevice(); + console.info(`Succeeded in getting selected media input device: ${JSON.stringify(device)}`); + + // [StartExclude select_MediaInputDevice] + let deviceInfo = 'Succeeded in getting selected media input device\n'; + if (globalLogUpdate) { + globalLogUpdate(deviceInfo, false); + } + // [EndExclude select_MediaInputDevice] + } catch (err) { + let error = err as BusinessError; + console.error(`Failed to get selected media input device. Code: ${error.code}, message: ${error.message}`); + // [StartExclude select_MediaInputDevice] + const errorMsg = `Failed to get selected media input device. Code: ${error.code}, message: ${error.message}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude select_MediaInputDevice] + } + // [StartExclude select_MediaInputDevice] +} + +async function clearSelectedInputDevice() { + // [EndExclude select_MediaInputDevice] + // 清空通过selectMediaInputDevice选择的输入设备。 + audioSessionManager.clearSelectedMediaInputDevice().then(() => { + console.info('Succeeded in clearing selected media input device.'); + // [StartExclude select_MediaInputDevice] + const successMsg = 'Succeeded in clearing selected media input device.'; + if (globalLogUpdate) { + globalLogUpdate(successMsg, false); + } + // [EndExclude select_MediaInputDevice] + }).catch((err: BusinessError) => { + console.error(`Failed to clear selected media input device. Code: ${err.code}, message: ${err.message}`); + // [StartExclude select_MediaInputDevice] + const errorMsg = `Failed to clear selected media input device. Code: ${err.code}, message: ${err.message}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude select_MediaInputDevice] + }); + // [End select_MediaInputDevice] +} + +@Entry +@Component +struct Index { + @State currentState: string = '未初始化'; + @State logMessages: string = '暂无日志信息'; + @State availableDeviceMessages: string = '暂无可选设备信息'; + @State currentDeviceMessages: string = '暂无当前设备信息'; + + aboutToAppear(): void { + // 设置全局回调 + globalLogUpdate = (msg, isError) => this.updateLogInfo(msg, isError); + globalAvailableDeviceUpdate = (msg) => this.updateAvailableDeviceInfo(msg); + globalCurrentDeviceUpdate = (msg) => this.updateCurrentDeviceInfo(msg); + } + + // 更新日志信息 + updateLogInfo(msg: string, isError: boolean): void { + const timestamp = new Date().toLocaleTimeString(); + const prefix = isError ? '[ERROR]' : '[INFO]'; + this.logMessages = `[${timestamp}] ${prefix} ${msg}`; + } + + // 更新可选设备信息 + updateAvailableDeviceInfo(msg: string): void { + const timestamp = new Date().toLocaleTimeString(); + this.availableDeviceMessages = `[${timestamp}] ${msg}`; + } + + // 更新当前设备信息 + updateCurrentDeviceInfo(msg: string): void { + const timestamp = new Date().toLocaleTimeString(); + this.currentDeviceMessages = `[${timestamp}] ${msg}`; + } + + build(): void { + Scroll() { + Column() { + // 信息显示区域 + Column() { + Text('实时状态信息') + .fontSize(18) + .fontWeight(600) + .margin({ bottom: 12 }) + + // 当前状态 + Column() { + Text('当前状态') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.currentState) + .fontSize(14) + .fontColor('#007DFF') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 日志信息 + Column() { + Text('日志信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.logMessages) + .fontSize(12) + .fontColor('#52C41A') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 可选设备变化回调 + Column() { + Text('可选设备变化回调') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.availableDeviceMessages) + .fontSize(12) + .fontColor('#FA8C16') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 当前设备变化回调 + Column() { + Text('当前设备变化回调') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.currentDeviceMessages) + .fontSize(12) + .fontColor('#9254DE') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 20 }) + } + .width('100%') + .padding(16) + .backgroundColor(Color.White) + .borderRadius(12) + .margin({ bottom: 16 }) + + // 功能按钮 + Row() { + Column() { + Text('设置蓝牙/近场优选低延迟').fontColor(Color.Black).fontSize(13); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 8, bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在设置蓝牙/近场优选...'; + await setBluetoothAndNearlinkPreferredRecordCategory(); + this.currentState = '设置完成'; + }); + + Column() { + Text('获取并选择输入设备').fontColor(Color.Black).fontSize(13); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在获取并选择输入设备...'; + await getAndSelectInputDevice(); + this.currentState = '操作完成'; + }); + } + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + + Row() { + Column() { + Text('查询已选输入设备').fontColor(Color.Black).fontSize(13); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 8, bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在查询已选输入设备...'; + await getSelectedInputDevice(); + this.currentState = '查询完成'; + }); + + Column() { + Text('清空已选输入设备').fontColor(Color.Black).fontSize(13); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在清空已选输入设备...'; + await clearSelectedInputDevice(); + this.currentState = '清空完成'; + }); + } + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + } + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5') + .padding(16); + } + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/ListenDeviceByAudioSession.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/ListenDeviceByAudioSession.ets new file mode 100644 index 0000000000000000000000000000000000000000..27f3744c85400c6a198c15595f11c7366dbdd3be --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/ListenDeviceByAudioSession.ets @@ -0,0 +1,340 @@ +/* +* 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. +*/ + +// [Start set_DefaultOutputDevice] +// [Start setting_DefaultOutputDevice] +import { BusinessError } from '@kit.BasicServicesKit'; +// [StartExclude set_DefaultOutputDevice] +// [Start get_SessionManager] +// [Start listen_CurrentOutputDeviceChangedEvent] +import { audio } from '@kit.AudioKit'; // 导入audio模块。 +// [StartExclude listen_CurrentOutputDeviceChangedEvent] +let audioManager = audio.getAudioManager(); // 需要先创建AudioManager实例。 + +let audioSessionManager = audioManager.getSessionManager(); // 再调用AudioManager的方法创建AudioSessionManager实例。 +// [End get_SessionManager] +// [StartExclude setting_DefaultOutputDevice] + +// 全局UI更新回调 +let globalLogUpdate: ((msg: string, isError: boolean) => void) | undefined; +let globalCallbackUpdate: ((msg: string) => void) | undefined; + +// [EndExclude listen_CurrentOutputDeviceChangedEvent] +// 同一监听事件中,on方法和off方法传入callback参数一致,off方法取消对应on方法订阅的监听。 +let currentOutputDeviceChangedCallback = (currentOutputDeviceChangedEvent: audio.CurrentOutputDeviceChangedEvent) => { + console.info(`reason of audioSessionStateChanged: ${currentOutputDeviceChangedEvent.changeReason} `); + + // [Start listen_CurrentOutputDeviceChangedEvent] + // 为UI收集信息 + let callbackMsg = `reason of audioSessionStateChanged: ${currentOutputDeviceChangedEvent.changeReason} `; + if (globalCallbackUpdate) { + globalCallbackUpdate(callbackMsg); + } + // [EndExclude listen_CurrentOutputDeviceChangedEvent] + + switch (currentOutputDeviceChangedEvent.changeReason) { + case audio.AudioStreamDeviceChangeReason.REASON_OLD_DEVICE_UNAVAILABLE: + // 响应设备不可用事件,如果应用处于播放状态,应暂停播放,更新UX界面。 + break; + case audio.AudioStreamDeviceChangeReason.REASON_NEW_DEVICE_AVAILABLE: + // 应用根据业务情况响应设备可用事件。 + break; + case audio.AudioStreamDeviceChangeReason.REASON_OVERRODE: + // 应用根据业务情况响应设备强选事件。 + break; + case audio.AudioStreamDeviceChangeReason.REASON_SESSION_ACTIVATED: + // 应用根据业务情况响应audio session激活时的输出设备信息。 + break; + case audio.AudioStreamDeviceChangeReason.REASON_STREAM_PRIORITY_CHANGED: + // 应用根据业务情况响应其它更高优先级的音频流触发的设备变更事件。 + break; + case audio.AudioStreamDeviceChangeReason.REASON_UNKNOWN: + // 应用根据业务情况响应未知原因事件。 + break; + } +}; +// [StartExclude listen_CurrentOutputDeviceChangedEvent] + +function listenCurrentOutputDeviceChanged() { + // [EndExclude listen_CurrentOutputDeviceChangedEvent] + audioSessionManager.on('currentOutputDeviceChanged', currentOutputDeviceChangedCallback); + // [StartExclude listen_CurrentOutputDeviceChangedEvent] +} + +function cancelListenCurrentOutputDeviceChanged() { + // [EndExclude listen_CurrentOutputDeviceChangedEvent] + audioSessionManager.off('currentOutputDeviceChanged', currentOutputDeviceChangedCallback); + // [StartExclude listen_CurrentOutputDeviceChangedEvent] +} + +async function cancelAllListenCurrentOutputDeviceChanged() { + // [EndExclude listen_CurrentOutputDeviceChangedEvent] + // 取消该事件的所有监听。 + audioSessionManager.off('currentOutputDeviceChanged'); + // [End listen_CurrentOutputDeviceChangedEvent] +} + +async function setDefaultOutputDeviceSpeaker() { + // [EndExclude set_DefaultOutputDevice] + // [EndExclude setting_DefaultOutputDevice] + // 设置默认输出设备为本机扬声器。 + audioSessionManager.setDefaultOutputDevice(audio.DeviceType.SPEAKER).then(() => { + // [StartExclude setting_DefaultOutputDevice] + console.info('setDefaultOutputDevice Success!'); + // [EndExclude setting_DefaultOutputDevice] + // [StartExclude set_DefaultOutputDevice] + console.info('Succeeded in setting default output device.'); + // [StartExclude setting_DefaultOutputDevice] + const successMsg = 'setDefaultOutputDevice Success!\nSucceeded in setting default output device to SPEAKER.'; + if (globalLogUpdate) { + globalLogUpdate(successMsg, false); + } + // [EndExclude setting_DefaultOutputDevice] + // [EndExclude set_DefaultOutputDevice] + }).catch((err: BusinessError) => { + // [StartExclude setting_DefaultOutputDevice] + console.error(`setDefaultOutputDevice Fail: ${err}`); + // [EndExclude setting_DefaultOutputDevice] + // [StartExclude set_DefaultOutputDevice] + console.error(`Failed to set default output device. Code: ${err.code}, message: ${err.message}`); + // [StartExclude setting_DefaultOutputDevice] + const errorMsg = `setDefaultOutputDevice Fail: ${err}; Failed to set default output device. + Code: ${err.code}, message: ${err.message}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude set_DefaultOutputDevice] + // [EndExclude setting_DefaultOutputDevice] + }); + // [StartExclude set_DefaultOutputDevice] + // [StartExclude setting_DefaultOutputDevice] +} + +async function setDefaultOutputDeviceDefault() { + // [EndExclude set_DefaultOutputDevice] + // [EndExclude setting_DefaultOutputDevice] + // 设置默认输出设备为默认设备,即取消应用设置的默认设备,交由系统选择设备。 + audioSessionManager.setDefaultOutputDevice(audio.DeviceType.DEFAULT).then(() => { + // [StartExclude setting_DefaultOutputDevice] + console.info('setDefaultOutputDevice Success!'); + // [EndExclude setting_DefaultOutputDevice] + // [StartExclude set_DefaultOutputDevice] + console.info('Succeeded in setting default output device.'); + // [StartExclude setting_DefaultOutputDevice] + const successMsg = 'setDefaultOutputDevice Success!'; + if (globalLogUpdate) { + globalLogUpdate(successMsg, false); + } + // [EndExclude setting_DefaultOutputDevice] + // [EndExclude set_DefaultOutputDevice] + }).catch((err: BusinessError) => { + // [StartExclude setting_DefaultOutputDevice] + console.error(`setDefaultOutputDevice Fail: ${err}`); + // [Exclude setting_DefaultOutputDevice] + + console.error(`Failed to set default output device. Code: ${err.code}, message: ${err.message}`); + // [StartExclude set_DefaultOutputDevice] + // [EndExclude setting_DefaultOutputDevice] + const errorMsg = `setDefaultOutputDevice Fail: ${err}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude setting_DefaultOutputDevice] + // [EndExclude set_DefaultOutputDevice] + }); + // [End set_DefaultOutputDevice] + // [End setting_DefaultOutputDevice] +} + +async function getDefaultOutputDevice() { + // 获取前先监听 + listenCurrentOutputDeviceChanged(); + // [Start get_DefaultOutputDevice] + let deviceType = audioSessionManager.getDefaultOutputDevice(); + console.info(`getDefaultOutputDevice Success, deviceType: ${deviceType}`); + // [End get_DefaultOutputDevice] + const successMsg = `getDefaultOutputDevice Success\nDevice type: ${deviceType}`; + if (globalLogUpdate) { + globalLogUpdate(successMsg, false); + } + // 获取后取消监听 + cancelListenCurrentOutputDeviceChanged(); + // 取消该事件的所有监听 + cancelAllListenCurrentOutputDeviceChanged(); +} + +@Entry +@Component +struct Index { + @State currentState: string = '未初始化'; + @State logMessages: string = '暂无日志信息'; + @State callbackMessages: string = '暂无回调信息'; + + aboutToAppear(): void { + // 设置全局回调 + globalLogUpdate = (msg, isError) => this.updateLogInfo(msg, isError); + globalCallbackUpdate = (msg) => this.updateCallbackInfo(msg); + } + + // 更新日志信息 + updateLogInfo(msg: string, isError: boolean): void { + const timestamp = new Date().toLocaleTimeString(); + const prefix = isError ? '[ERROR]' : '[INFO]'; + this.logMessages = `[${timestamp}] ${prefix} ${msg}`; + } + + // 更新回调信息 + updateCallbackInfo(msg: string): void { + const timestamp = new Date().toLocaleTimeString(); + this.callbackMessages = `[${timestamp}] ${msg}`; + } + + build(): void { + Scroll() { + Column() { + // 信息显示区域 + Column() { + Text('实时状态信息') + .fontSize(18) + .fontWeight(600) + .margin({ bottom: 12 }) + + // 当前状态 + Column() { + Text('当前状态') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.currentState) + .fontSize(14) + .fontColor('#007DFF') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 日志信息 + Column() { + Text('日志信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.logMessages) + .fontSize(12) + .fontColor('#52C41A') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 回调信息 + Column() { + Text('输出设备变化回调') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.callbackMessages) + .fontSize(12) + .fontColor('#FA8C16') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 20 }) + } + .width('100%') + .padding(16) + .backgroundColor(Color.White) + .borderRadius(12) + .margin({ bottom: 16 }) + + // 功能按钮 + Row() { + Column() { + Text('设置为扬声器').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 8, bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在设置为扬声器...'; + await setDefaultOutputDeviceSpeaker(); + this.currentState = '设置完成'; + }); + + Column() { + Text('设置为默认输出').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在设置为默认输出...'; + await setDefaultOutputDeviceDefault(); + this.currentState = '设置完成'; + }); + } + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + + Row() { + Column() { + Text('获取当前默认输出').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 8, bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在获取当前默认输出...'; + await getDefaultOutputDevice(); + this.currentState = '获取完成'; + }); + } + .width('100%') + .justifyContent(FlexAlign.Start) + } + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5') + .padding(16); + } + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/OutputDeviceChangePause.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/OutputDeviceChangePause.ets new file mode 100644 index 0000000000000000000000000000000000000000..525fe198a3571642b64e285bb083d98791e2f67a --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/ets/pages/OutputDeviceChangePause.ets @@ -0,0 +1,295 @@ +/* +* 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. +*/ + +// [Start all_outputDeviceChange] +// [Start set_DefaultOutputDevice] +import { audio } from '@kit.AudioKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +// [StartExclude set_DefaultOutputDevice] +let audioRenderer: audio.AudioRenderer | undefined = undefined; +let audioStreamInfo: audio.AudioStreamInfo = { + samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率。 + channels: audio.AudioChannel.CHANNEL_2, // 通道。 + sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式。 + encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式。 +}; +let audioRendererInfo: audio.AudioRendererInfo = { + usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 音频流使用类型:音乐。根据业务场景配置,参考StreamUsage。 + rendererFlags: 0 // 音频渲染器标志。 +}; +let audioRendererOptions: audio.AudioRendererOptions = { + streamInfo: audioStreamInfo, + rendererInfo: audioRendererInfo +}; +// [StartExclude all_outputDeviceChange] +// 全局UI更新回调 +let globalLogUpdate: ((msg: string, isError: boolean) => void) | undefined; +let globalCallbackUpdate: ((msg: string) => void) | undefined; + +async function createAudioRenderer() { + // [EndExclude all_outputDeviceChange] + // 创建AudioRenderer实例。 + audio.createAudioRenderer(audioRendererOptions).then((data) => { + audioRenderer = data; + console.info('AudioFrameworkRenderLog: AudioRenderer Created : Success : Stream Type: SUCCESS'); + // [StartExclude all_outputDeviceChange] + const successMsg = 'AudioFrameworkRenderLog: AudioRenderer Created : Success : Stream Type: SUCCESS'; + if (globalLogUpdate) { + globalLogUpdate(successMsg, false); + } + // [EndExclude all_outputDeviceChange] + }).catch((err: BusinessError) => { + console.error(`AudioFrameworkRenderLog: AudioRenderer Created : ERROR : ${err}`); + // [StartExclude all_outputDeviceChange] + const errorMsg = `AudioFrameworkRenderLog: AudioRenderer Created : ERROR : ${err}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude all_outputDeviceChange] + }); + + if (audioRenderer) { + // 订阅监听音频流输出设备变化及原因。 + (audioRenderer as audio.AudioRenderer).on('outputDeviceChangeWithInfo', async (deviceChangeInfo: audio + .AudioStreamDeviceChangeInfo) => { + switch (deviceChangeInfo.changeReason) { + case audio.AudioStreamDeviceChangeReason.REASON_OLD_DEVICE_UNAVAILABLE: + // 响应设备不可用事件,如果应用处于播放状态,应暂停播放,更新UX界面。 + // await audioRenderer.pause(); + break; + case audio.AudioStreamDeviceChangeReason.REASON_NEW_DEVICE_AVAILABLE: + // 应用根据业务情况响应设备可用事件。 + break; + case audio.AudioStreamDeviceChangeReason.REASON_OVERRODE: + // 应用根据业务情况响应设备强选事件。 + break; + case audio.AudioStreamDeviceChangeReason.REASON_UNKNOWN: + // 应用根据业务情况响应未知原因事件。 + break; + } + }); + } + // [End all_outputDeviceChange] +} + +async function rendererSetDefaultOutputDeviceSpeaker() { + if (audioRenderer !== undefined) { + // [EndExclude set_DefaultOutputDevice] + // 设置默认输出设备为本机扬声器。 + audioRenderer.setDefaultOutputDevice(audio.DeviceType.SPEAKER).then(() => { + console.info('Succeeded in setting default output device.'); + // [StartExclude set_DefaultOutputDevice] + const successMsg = 'Succeeded in setting default output device to SPEAKER.'; + if (globalLogUpdate) { + globalLogUpdate(successMsg, false); + } + // [EndExclude set_DefaultOutputDevice] + }).catch((err: BusinessError) => { + console.error(`Failed to set default output device. Code: ${err.code}, message: ${err.message}`); + // [StartExclude set_DefaultOutputDevice] + const errorMsg = `Failed to set default output device. Code: ${err.code}, message: ${err.message}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude set_DefaultOutputDevice] + }); + // [StartExclude set_DefaultOutputDevice] + } +} + +async function rendererDefaultOutputDeviceDefault() { + if (audioRenderer !== undefined) { + // [EndExclude set_DefaultOutputDevice] + // 设置默认输出设备为系统默认输出设备,即取消应用设置的默认设备,交由系统选择设备。 + audioRenderer.setDefaultOutputDevice(audio.DeviceType.DEFAULT).then(() => { + console.info('Succeeded in setting default output device.'); + // [StartExclude set_DefaultOutputDevice] + const successMsg = 'Succeeded in setting default output device to DEFAULT (system choice).'; + if (globalLogUpdate) { + globalLogUpdate(successMsg, false); + } + // [EndExclude set_DefaultOutputDevice] + }).catch((err: BusinessError) => { + console.error(`Failed to set default output device. Code: ${err.code}, message: ${err.message}`); + // [StartExclude set_DefaultOutputDevice] + const errorMsg = `Failed to set default output device. Code: ${err.code}, message: ${err.message}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude set_DefaultOutputDevice] + }); + // [End set_DefaultOutputDevice] + } +} + +@Entry +@Component +struct Index { + @State currentState: string = '未初始化'; + @State logMessages: string = '暂无日志信息'; + @State callbackMessages: string = '暂无回调信息'; + + aboutToAppear(): void { + // 设置全局回调 + globalLogUpdate = (msg, isError) => this.updateLogInfo(msg, isError); + globalCallbackUpdate = (msg) => this.updateCallbackInfo(msg); + } + + // 更新日志信息 + updateLogInfo(msg: string, isError: boolean): void { + const timestamp = new Date().toLocaleTimeString(); + const prefix = isError ? '[ERROR]' : '[INFO]'; + this.logMessages = `[${timestamp}] ${prefix} ${msg}`; + } + + // 更新回调信息 + updateCallbackInfo(msg: string): void { + const timestamp = new Date().toLocaleTimeString(); + this.callbackMessages = `[${timestamp}] ${msg}`; + } + + build(): void { + Scroll() { + Column() { + // 信息显示区域 + Column() { + Text('实时状态信息') + .fontSize(18) + .fontWeight(600) + .margin({ bottom: 12 }) + + // 当前状态 + Column() { + Text('当前状态') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.currentState) + .fontSize(14) + .fontColor('#007DFF') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 日志信息 + Column() { + Text('日志信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.logMessages) + .fontSize(12) + .fontColor('#52C41A') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 回调信息 + Column() { + Text('输出设备变化回调') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.callbackMessages) + .fontSize(12) + .fontColor('#FA8C16') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 20 }) + } + .width('100%') + .padding(16) + .backgroundColor(Color.White) + .borderRadius(12) + .margin({ bottom: 16 }) + + // 功能按钮 + Column() { + Text('创建音频播放实例').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('100%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在创建音频播放实例...'; + await createAudioRenderer(); + this.currentState = '创建完成'; + }); + + Row() { + Column() { + Text('设置为本机扬声器').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 8, bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在设置为本机扬声器...'; + await rendererSetDefaultOutputDeviceSpeaker(); + this.currentState = '设置完成'; + }); + + Column() { + Text('设置为系统默认设备').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在设置为系统默认设备...'; + await rendererDefaultOutputDeviceDefault(); + this.currentState = '设置完成'; + }); + } + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + } + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5') + .padding(16); + } + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/module.json5 b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..5cd81baaff5c7c86a6ed4aeea60b10b90d859058 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/module.json5 @@ -0,0 +1,100 @@ +/* +* 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": [ + "default" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "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": [ + "ohos.want.action.home" + ] + }, + { + "actions": [ + // actions不能为空,actions为空会造成目标方匹配失败。 + "ohos.want.action.viewData" + ], + "uris": [ + { + // scheme必选,可以自定义,以link为例,需要替换为实际的scheme + "scheme": "audioTest", + // host必选,配置待匹配的域名 + "host": "www.audioTest.com" + } + ] + } // 新增一个skill对象,用于跳转场景。如果存在多个跳转场景,需配置多个skill对象。 + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ] + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.MICROPHONE", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "always" + } + }, + { + "name": "ohos.permission.ACCESS_NOTIFICATION_POLICY", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "always" + } + }, + ] + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/element/color.json b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/element/float.json b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..801bc27a39ab78e230cb049d46c0b4f7acb5e01d --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/element/float.json @@ -0,0 +1,16 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + }, + { + "name": "index_text_font_size", + "value": "50fp" + }, + { + "name": "index_button_height_size", + "value": "80vp" + } + ] +} diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/element/string.json b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f94595515a99e0c828807e243494f57f09251930 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/media/32_xiyouji.pcm b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/media/32_xiyouji.pcm new file mode 100644 index 0000000000000000000000000000000000000000..bdd03b917b05a9b428a6ee044d1890ed841d6447 Binary files /dev/null and b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/media/32_xiyouji.pcm differ diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/media/background.png b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/media/background.png differ diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/media/foreground.png b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97014d3e10e5ff511409c378cd4255713aecd85f Binary files /dev/null and b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/media/foreground.png differ diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/media/layered_image.json b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/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/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/media/startIcon.png b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/media/startIcon.png differ diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/profile/backup_config.json b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/profile/main_pages.json b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..3dc3a33b7e485797569ffa046a59ca1c1119c7a8 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,10 @@ +{ + "src": [ + "pages/Index", + "pages/FindAndListenAudioInputDevice", + "pages/FindAndListenAudioOutputDevice", + "pages/InputDeviceRoutingSwitching", + "pages/ListenDeviceByAudioSession", + "pages/OutputDeviceChangePause" + ] +} diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/dark/element/color.json b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/dark/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..79b11c2747aec33e710fd3a7b2b3c94dd9965499 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/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/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/rawfile/32_xiyouji.pcm b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/rawfile/32_xiyouji.pcm new file mode 100644 index 0000000000000000000000000000000000000000..bdd03b917b05a9b428a6ee044d1890ed841d6447 Binary files /dev/null and b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/main/resources/rawfile/32_xiyouji.pcm differ diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..621da0d393a21467d95052cf350dd15f438996e8 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,49 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/FindAndListenAudioInputDevice.test.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/FindAndListenAudioInputDevice.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..80813e17b23d2fe250b0e5811dc1c6720e5d8e25 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/FindAndListenAudioInputDevice.test.ets @@ -0,0 +1,93 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, Level } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const TAG = '[FindAndListenAudioInputDevice]'; +const DOMAIN = 0xF815; +const BUNDLE = 'FindAndListenAudioInputDevice_'; +let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const driver = Driver.create(); + +async function runTest(testCaseName: string, buttonText: string, delayTime: number, description: string, + done: Function) { + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, begin`); + + let btn = await driver.findComponent(ON.text(buttonText)); + if (btn !== undefined) { + await btn.click(); + await driver.delayMs(delayTime); + } else { + hilog.warn(DOMAIN, TAG, BUNDLE + `Button "${buttonText}" not found, still waiting...`); + await driver.delayMs(delayTime); + } + + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, end`); + done(); +} + +export default function FindAndListenAudioInputDeviceTest() { + describe('FindAndListenAudioInputDeviceTest', () => { + beforeAll(() => { + abilityDelegator.startAbility({ + bundleName: 'com.example.myapplication', + abilityName: 'EntryAbility' + }); + }); + + beforeEach(() => {}) + afterEach(() => {}) + afterAll(() => {}) + + /** + * @tc.number FindAndListenAudioInputDevice_EnterPage_0001 + * @tc.name FindAndListenAudioInputDevice_EnterPage_0001 + * @tc.desc Navigate to audio input device listening page from main page + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('FindAndListenAudioInputDevice_EnterPage_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'FindAndListenAudioInputDevice_EnterPage_0001', + '查询和监听音频输入设备页面', + 5000, + 'Navigate to audio input device listening page', + done + ); + }); + + /** + * @tc.number FindAndListenAudioInputDevice_GetDevices_0001 + * @tc.name FindAndListenAudioInputDevice_GetDevices_0001 + * @tc.desc Click get input devices list button to fetch and display available audio input devices + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('FindAndListenAudioInputDevice_GetDevices_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'FindAndListenAudioInputDevice_GetDevices_0001', + '获取输入设备列表', + 6000, + 'Click get input devices list button', + done + ); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/FindAndListenAudioOutputDevice.test.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/FindAndListenAudioOutputDevice.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..a7918a9168473f143e31074096ad5abcbd3d49b6 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/FindAndListenAudioOutputDevice.test.ets @@ -0,0 +1,111 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, Level } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const TAG = '[FindAndListenAudioOutputDevice]'; +const DOMAIN = 0xF816; +const BUNDLE = 'FindAndListenAudioOutputDevice_'; +let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const driver = Driver.create(); + +async function runTest(testCaseName: string, buttonText: string, delayTime: number, description: string, + done: Function) { + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, begin`); + + let btn = await driver.findComponent(ON.text(buttonText)); + if (btn !== undefined) { + await btn.click(); + await driver.delayMs(delayTime); + } else { + hilog.warn(DOMAIN, TAG, BUNDLE + `Button "${buttonText}" not found.`); + await driver.delayMs(delayTime); + } + + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, end`); + done(); +} + +export default function FindAndListenAudioOutputDeviceTest() { + describe('FindAndListenAudioOutputDeviceTest', () => { + beforeAll(() => { + abilityDelegator.startAbility({ + bundleName: 'com.example.myapplication', + abilityName: 'EntryAbility' + }); + }); + + beforeEach(() => {}) + afterEach(() => {}) + afterAll(() => {}) + + /** + * @tc.number FindAndListenAudioOutputDevice_EnterPage_0001 + * @tc.name FindAndListenAudioOutputDevice_EnterPage_0001 + * @tc.desc Navigate to audio output device listening page from main page + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('FindAndListenAudioOutputDevice_EnterPage_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'FindAndListenAudioOutputDevice_EnterPage_0001', + '查询和监听音频输出设备页面', + 5000, + 'Navigate to audio output device listening page', + done + ); + }); + + /** + * @tc.number FindAndListenAudioOutputDevice_GetOutputDevices_0001 + * @tc.name FindAndListenAudioOutputDevice_GetOutputDevices_0001 + * @tc.desc Click get output devices list button to fetch available audio output devices + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('FindAndListenAudioOutputDevice_GetOutputDevices_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'FindAndListenAudioOutputDevice_GetOutputDevices_0001', + '获取输出设备列表', + 6000, + 'Click get output devices list button', + done + ); + }); + + /** + * @tc.number FindAndListenAudioOutputDevice_GetPreferDevice_0001 + * @tc.name FindAndListenAudioOutputDevice_GetPreferDevice_0001 + * @tc.desc Click get preferred output device button to query system preferred audio output device + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('FindAndListenAudioOutputDevice_GetPreferDevice_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'FindAndListenAudioOutputDevice_GetPreferDevice_0001', + '获取优先输出设备', + 6000, + 'Click get preferred output device button', + done + ); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/InputDeviceRoutingSwitching.test.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/InputDeviceRoutingSwitching.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..e0fbe12d6d288b527249ebc836a2ae7410e20337 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/InputDeviceRoutingSwitching.test.ets @@ -0,0 +1,147 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, Level } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const TAG = '[AudioInputDeviceSelection]'; +const DOMAIN = 0xF817; +const BUNDLE = 'AudioInputDeviceSelection_'; +let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const driver = Driver.create(); + +async function runTest(testCaseName: string, buttonText: string, delayTime: number, description: string, + done: Function) { + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, begin`); + + let btn = await driver.findComponent(ON.text(buttonText)); + if (btn !== undefined) { + await btn.click(); + await driver.delayMs(delayTime); + } else { + hilog.warn(DOMAIN, TAG, BUNDLE + `Button "${buttonText}" not found.`); + await driver.delayMs(delayTime); + } + + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, end`); + done(); +} + +export default function AudioInputDeviceSelectionTest() { + describe('AudioInputDeviceSelectionTest', () => { + beforeAll(() => { + abilityDelegator.startAbility({ + bundleName: 'com.example.myapplication', + abilityName: 'EntryAbility' + }); + }); + + beforeEach(() => {}) + afterEach(() => {}) + afterAll(() => {}) + + /** + * @tc.number AudioInputDeviceSelection_EnterPage_0001 + * @tc.name AudioInputDeviceSelection_EnterPage_0001 + * @tc.desc Navigate to audio input device selection page from main page + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioInputDeviceSelection_EnterPage_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioInputDeviceSelection_EnterPage_0001', + '实现音频输入设备路由切换页面', + 5000, + 'Navigate to audio input device selection page', + done + ); + }); + + /** + * @tc.number AudioInputDeviceSelection_SetPreferredCategory_0001 + * @tc.name AudioInputDeviceSelection_SetPreferredCategory_0001 + * @tc.desc Set Bluetooth or Nearlink preferred record category to low latency + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioInputDeviceSelection_SetPreferredCategory_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioInputDeviceSelection_SetPreferredCategory_0001', + '设置蓝牙/近场优选低延迟', + 4000, + 'Set Bluetooth or Nearlink preferred record category to low latency', + done + ); + }); + + /** + * @tc.number AudioInputDeviceSelection_SelectInputDevice_0001 + * @tc.name AudioInputDeviceSelection_SelectInputDevice_0001 + * @tc.desc Get available input devices and select one + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioInputDeviceSelection_SelectInputDevice_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioInputDeviceSelection_SelectInputDevice_0001', + '获取并选择输入设备', + 6000, + 'Get available input devices and select one', + done + ); + }); + + /** + * @tc.number AudioInputDeviceSelection_GetSelectedDevice_0001 + * @tc.name AudioInputDeviceSelection_GetSelectedDevice_0001 + * @tc.desc Query currently selected input device + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioInputDeviceSelection_GetSelectedDevice_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioInputDeviceSelection_GetSelectedDevice_0001', + '查询已选输入设备', + 3000, + 'Query currently selected input device', + done + ); + }); + + /** + * @tc.number AudioInputDeviceSelection_ClearSelectedDevice_0001 + * @tc.name AudioInputDeviceSelection_ClearSelectedDevice_0001 + * @tc.desc Clear selected input device + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioInputDeviceSelection_ClearSelectedDevice_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioInputDeviceSelection_ClearSelectedDevice_0001', + '清空已选输入设备', + 3000, + 'Clear selected input device', + done + ); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/List.test.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..5012184670cc3a2f1041c7c9e8b2b9fe6e929c1d --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,19 @@ +import abilityTest from './Ability.test'; +import AudioRoutingDemoTest from './FindAndListenAudioInputDevice.test'; +import VoIPOutputDeviceDemoTest from './FindAndListenAudioOutputDevice.test'; +import MediaInputDeviceDemoTest from './InputDeviceRoutingSwitching.test'; +import SessionOutputDeviceDemoTest from './ListenDeviceByAudioSession.test'; +import AudioRendererOutputDeviceDemoTest from './OutputDeviceChangePause.test'; +import VoIPAudioCapturerDemoTest from './VoIpDemoForAudioCapturer.test'; +import VoIPAudioRendererDemoTest from './VoIpDemoForAudioRenderer.test'; + +export default function testsuite() { + abilityTest(); + AudioRoutingDemoTest(); + VoIPOutputDeviceDemoTest(); + MediaInputDeviceDemoTest(); + SessionOutputDeviceDemoTest(); + AudioRendererOutputDeviceDemoTest(); + VoIPAudioCapturerDemoTest(); + VoIPAudioRendererDemoTest(); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/ListenDeviceByAudioSession.test.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/ListenDeviceByAudioSession.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..a5ac4d304b6f739077a08156c2ddd80917cff3c5 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/ListenDeviceByAudioSession.test.ets @@ -0,0 +1,129 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, Level } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const TAG = '[SetDefaultOutputDevice]'; +const DOMAIN = 0xF818; +const BUNDLE = 'SetDefaultOutputDevice_'; +let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const driver = Driver.create(); + +async function runTest(testCaseName: string, buttonText: string, delayTime: number, description: string, + done: Function) { + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, begin`); + + let btn = await driver.findComponent(ON.text(buttonText)); + if (btn !== undefined) { + await btn.click(); + await driver.delayMs(delayTime); + } else { + hilog.warn(DOMAIN, TAG, BUNDLE + `Button "${buttonText}" not found.`); + await driver.delayMs(delayTime); + } + + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, end`); + done(); +} + +export default function SetDefaultOutputDeviceTest() { + describe('SetDefaultOutputDeviceTest', () => { + beforeAll(() => { + abilityDelegator.startAbility({ + bundleName: 'com.example.myapplication', + abilityName: 'EntryAbility' + }); + }); + + beforeEach(() => {}) + afterEach(() => {}) + afterAll(() => {}) + + /** + * @tc.number SetDefaultOutputDevice_EnterPage_0001 + * @tc.name SetDefaultOutputDevice_EnterPage_0001 + * @tc.desc Navigate to set default output device page from main page + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('SetDefaultOutputDevice_EnterPage_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'SetDefaultOutputDevice_EnterPage_0001', + '通过音频会话查询和监听音频输出设备页面', + 5000, + 'Navigate to set default output device page', + done + ); + }); + + /** + * @tc.number SetDefaultOutputDevice_SetToSpeaker_0001 + * @tc.name SetDefaultOutputDevice_SetToSpeaker_0001 + * @tc.desc Set default output device to speaker + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('SetDefaultOutputDevice_SetToSpeaker_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'SetDefaultOutputDevice_SetToSpeaker_0001', + '设置为扬声器', + 3000, + 'Set default output device to SPEAKER', + done + ); + }); + + /** + * @tc.number SetDefaultOutputDevice_SetToDefault_0001 + * @tc.name SetDefaultOutputDevice_SetToDefault_0001 + * @tc.desc Reset default output device to system default + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('SetDefaultOutputDevice_SetToDefault_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'SetDefaultOutputDevice_SetToDefault_0001', + '设置为默认输出', + 3000, + 'Reset default output device to system default', + done + ); + }); + + /** + * @tc.number SetDefaultOutputDevice_GetCurrent_0001 + * @tc.name SetDefaultOutputDevice_GetCurrent_0001 + * @tc.desc Get current default output device type + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('SetDefaultOutputDevice_GetCurrent_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'SetDefaultOutputDevice_GetCurrent_0001', + '获取当前默认输出', + 4000, + 'Get current default output device type', + done + ); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/OutputDeviceChangePause.test.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/OutputDeviceChangePause.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..23eb07a012af3c5e7f95286d85bb37da7b99cd46 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/ets/test/OutputDeviceChangePause.test.ets @@ -0,0 +1,129 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, Level } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const TAG = '[AudioRendererSetDefaultOutputDevice]'; +const DOMAIN = 0xF819; +const BUNDLE = 'AudioRendererSetDefaultOutputDevice_'; +let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const driver = Driver.create(); + +async function runTest(testCaseName: string, buttonText: string, delayTime: number, description: string, + done: Function) { + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, begin`); + + let btn = await driver.findComponent(ON.text(buttonText)); + if (btn !== undefined) { + await btn.click(); + await driver.delayMs(delayTime); + } else { + hilog.warn(DOMAIN, TAG, BUNDLE + `Button "${buttonText}" not found.`); + await driver.delayMs(delayTime); + } + + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, end`); + done(); +} + +export default function AudioRendererSetDefaultOutputDeviceTest() { + describe('AudioRendererSetDefaultOutputDeviceTest', () => { + beforeAll(() => { + abilityDelegator.startAbility({ + bundleName: 'com.example.myapplication', + abilityName: 'EntryAbility' + }); + }); + + beforeEach(() => {}) + afterEach(() => {}) + afterAll(() => {}) + + /** + * @tc.number AudioRendererSetDefaultOutputDevice_EnterPage_0001 + * @tc.name AudioRendererSetDefaultOutputDevice_EnterPage_0001 + * @tc.desc Navigate to audio renderer output device routing page from main page + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererSetDefaultOutputDevice_EnterPage_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererSetDefaultOutputDevice_EnterPage_0001', + '响应输出设备变更时合理暂停和实现音频输出设备路由切换页面', + 5000, + 'Navigate to audio renderer output device routing page', + done + ); + }); + + /** + * @tc.number AudioRendererSetDefaultOutputDevice_CreateRenderer_0001 + * @tc.name AudioRendererSetDefaultOutputDevice_CreateRenderer_0001 + * @tc.desc Create an AudioRenderer instance + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererSetDefaultOutputDevice_CreateRenderer_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererSetDefaultOutputDevice_CreateRenderer_0001', + '创建音频播放实例', + 4000, + 'Create an AudioRenderer instance', + done + ); + }); + + /** + * @tc.number AudioRendererSetDefaultOutputDevice_SetToSpeaker_0001 + * @tc.name AudioRendererSetDefaultOutputDevice_SetToSpeaker_0001 + * @tc.desc Set AudioRenderer output device to built-in speaker + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererSetDefaultOutputDevice_SetToSpeaker_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererSetDefaultOutputDevice_SetToSpeaker_0001', + '设置为本机扬声器', + 3000, + 'Set AudioRenderer output device to built-in speaker', + done + ); + }); + + /** + * @tc.number AudioRendererSetDefaultOutputDevice_SetToDefault_0001 + * @tc.name AudioRendererSetDefaultOutputDevice_SetToDefault_0001 + * @tc.desc Reset AudioRenderer output device to system default + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('AudioRendererSetDefaultOutputDevice_SetToDefault_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'AudioRendererSetDefaultOutputDevice_SetToDefault_0001', + '设置为系统默认设备', + 3000, + 'Reset AudioRenderer output device to system default', + done + ); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/module.json5 b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..0a099ee4bd05a001f943915cd4eb2e01aabeaca0 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/ohosTest/module.json5 @@ -0,0 +1,25 @@ +/* +* 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_test", + "type": "feature", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/test/List.test.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..c2359615fdfb661f743a2cac5886892209c7aee6 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/test/List.test.ets @@ -0,0 +1,19 @@ +/* +* 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 localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/test/LocalUnit.test.ets b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..e986ae3c703e76f0f23372bca8220fd062147253 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,47 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from 'C:\\routingmanager_arkts\\audiorenderer_arkts\\entry\\src\\hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + // expect(a).assertContain(b); + // expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/hvigor/hvigor-config.json5 b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7d7d5f8aa0d0c54d1c8daf78625b9b3bbf52aee8 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/hvigor/hvigor-config.json5 @@ -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. +*/ +{ + "modelVersion": "6.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | 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 */ + // "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */ + }, + "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/MediaKit/AudioKit/AudioRoutingManagerSampleJS/hvigorfile.ts b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c5b9939f0c562551a58ebd26abae33caf4f5e28 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/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. */ +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/oh-package.json5 b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..f839e12ead738ca5c8a42f5dd9d2211f3074c110 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/oh-package.json5 @@ -0,0 +1,22 @@ +/* +* 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": "6.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + } +} diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/ohosTest.md b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/ohosTest.md new file mode 100644 index 0000000000000000000000000000000000000000..3edeb503c449e7ba7f0d05f2251873d1af016a88 --- /dev/null +++ b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/ohosTest.md @@ -0,0 +1,25 @@ +# 音频设备路由管理测试用例归档 + +## 用例表 + +| 测试功能 | 预置条件 | 输入 | 预期输出 | 是否自动 | +|----------------------------------|--------------|------------------------------------|----------------------------------------|---------| +| 拉起应用 | 设备正常运行 | - | 成功拉起应用,进入首页 | 是 | +| 进入查询和监听音频输入设备页面 | 位于主页 | 点击"查询和监听音频输入设备页面" | 进入音频输入设备监听页面 | 是 | +| 获取输入设备列表 | 输入设备页面 | 点击"获取输入设备列表" | 获取并显示可用音频输入设备列表 | 是 | +| 进入查询和监听音频输出设备页面 | 位于主页 | 点击"查询和监听音频输出设备页面" | 进入音频输出设备监听页面 | 是 | +| 获取输出设备列表 | 输出设备页面 | 点击"获取输出设备列表" | 获取并显示可用音频输出设备列表 | 是 | +| 获取优先输出设备 | 输出设备页面 | 点击"获取优先输出设备" | 查询并显示系统首选音频输出设备 | 是 | +| 进入音频输入设备路由切换页面 | 位于主页 | 点击"实现音频输入设备路由切换页面" | 进入音频输入设备选择与路由页面 | 是 | +| 设置蓝牙/近场优选低延迟 | 输入设备路由页面 | 点击"设置蓝牙/近场优选低延迟" | 将蓝牙或近场通信设备设为低延迟录制优先类别 | 是 | +| 获取并选择输入设备 | 输入设备路由页面 | 点击"获取并选择输入设备" | 获取可用输入设备并完成选择 | 是 | +| 查询已选输入设备 | 输入设备路由页面 | 点击"查询已选输入设备" | 显示当前选定的音频输入设备 | 是 | +| 清空已选输入设备 | 输入设备路由页面 | 点击"清空已选输入设备" | 取消当前输入设备选择,恢复默认 | 是 | +| 进入通过音频会话查询和监听输出设备页面 | 位于主页 | 点击"通过音频会话查询和监听音频输出设备页面" | 进入默认输出设备设置页面 | 是 | +| 设置为扬声器 | 默认输出设备页面 | 点击"设置为扬声器" | 将默认音频输出设备设为本机扬声器 | 是 | +| 设置为默认输出 | 默认输出设备页面 | 点击"设置为默认输出" | 恢复系统默认音频输出设备 | 是 | +| 获取当前默认输出 | 默认输出设备页面 | 点击"获取当前默认输出" | 查询并显示当前系统默认输出设备类型 | 是 | +| 进入响应输出设备变更的音频输出路由切换页面 | 位于主页 | 点击"响应输出设备变更时合理暂停和实现音频输出设备路由切换页面" | 进入基于 AudioRenderer 的输出设备路由页面 | 是 | +| 创建音频播放实例 | 音频渲染路由页面 | 点击"创建音频播放实例" | 成功创建 AudioRenderer 实例 | 是 | +| 设置为本机扬声器 | 音频渲染路由页面 | 点击"设置为本机扬声器" | 将 AudioRenderer 输出设备设为本机扬声器 | 是 | +| 设置为系统默认设备 | 音频渲染路由页面 | 点击"设置为系统默认设备" | 将 AudioRenderer 输出设备重置为系统默认 | 是 | \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/FindAndListenAudioInputDevice.png b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/FindAndListenAudioInputDevice.png new file mode 100644 index 0000000000000000000000000000000000000000..3ce5497380c6e8fabb28a7784a58f9db79efacc7 Binary files /dev/null and b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/FindAndListenAudioInputDevice.png differ diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/FindAndListenAudioOutputDevice.png b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/FindAndListenAudioOutputDevice.png new file mode 100644 index 0000000000000000000000000000000000000000..59b7da6c5492e59828eb216aa3053c1bb4dde538 Binary files /dev/null and b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/FindAndListenAudioOutputDevice.png differ diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/InputDeviceRoutingSwitching.png b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/InputDeviceRoutingSwitching.png new file mode 100644 index 0000000000000000000000000000000000000000..81b24dd2c5bd7883614a47fe8eaadae458fa8267 Binary files /dev/null and b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/InputDeviceRoutingSwitching.png differ diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/ListenDeviceByAudioSession.png b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/ListenDeviceByAudioSession.png new file mode 100644 index 0000000000000000000000000000000000000000..aecd5b8fe72179a7936d107b85c9d84079f64486 Binary files /dev/null and b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/ListenDeviceByAudioSession.png differ diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/OutputDeviceChangePause.png b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/OutputDeviceChangePause.png new file mode 100644 index 0000000000000000000000000000000000000000..6d472d8953a8bed0879b8f58e11b3fc46daa61b5 Binary files /dev/null and b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/OutputDeviceChangePause.png differ diff --git a/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/Routing_Index.png b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/Routing_Index.png new file mode 100644 index 0000000000000000000000000000000000000000..5f9e10f61dd7d99ebce70831649a24919d6deff7 Binary files /dev/null and b/MediaKit/AudioKit/AudioRoutingManagerSampleJS/screenShots/Routing_Index.png differ diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/.gitignore b/MediaKit/AudioKit/AudioSessionSampleJS/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/.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/MediaKit/AudioKit/AudioSessionSampleJS/AppScope/app.json5 b/MediaKit/AudioKit/AudioSessionSampleJS/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..02e14fba2e646d86166447488188b104168fecbc --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/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.myapplication", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:layered_image", + "label": "$string:app_name" + } +} diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/AppScope/resources/base/element/string.json b/MediaKit/AudioKit/AudioSessionSampleJS/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..1080233f01384411ec684b58955cb8808746fdd3 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "MyApplication" + } + ] +} diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/AppScope/resources/base/media/background.png b/MediaKit/AudioKit/AudioSessionSampleJS/AppScope/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/MediaKit/AudioKit/AudioSessionSampleJS/AppScope/resources/base/media/background.png differ diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/AppScope/resources/base/media/foreground.png b/MediaKit/AudioKit/AudioSessionSampleJS/AppScope/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9427585b36d14b12477435b6419d1f07b3e0bb Binary files /dev/null and b/MediaKit/AudioKit/AudioSessionSampleJS/AppScope/resources/base/media/foreground.png differ diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/AppScope/resources/base/media/layered_image.json b/MediaKit/AudioKit/AudioSessionSampleJS/AppScope/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/AppScope/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/MediaKit/AudioKit/AudioSessionSampleJS/README.md b/MediaKit/AudioKit/AudioSessionSampleJS/README.md new file mode 100644 index 0000000000000000000000000000000000000000..02ebe4c985b94018ee90fbb6ac53a9812bbb3bc8 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/README.md @@ -0,0 +1,56 @@ +# 实现音频焦点功能 + +## 介绍 + +本示例基于AudioSessionManager提供的能力,实现了通过AudioSession主动管理应用内音频流的焦点、自定义本应用音频流的焦点策略、调整本应用音频流释放音频焦点的时机等功能,包含了功能调用接口的完整链路。 + +## 效果图预览 + +**图1**:主界面 + +- 依次点击'设置场景'、'激活焦点+注册监听'按钮,即可激活音频焦点,焦点策略为混合播放。 +- 点击'判断焦点是否激活'按钮,即可查询焦点状态,查询信息在运行结果栏显示。 +- 点击'注销焦点+注销监听'按钮,即可注销音频焦点。 + + + +## 工程结构&模块类型 + +``` +├───entry/src/main/ets +│ ├───entryability +│ │ ├───EntryAbility.ets // Ability的生命周期回调内容。 +│ ├───entrybackupability +│ │ └───EntryBackupAbility.ets // BackupAbility的生命周期回调内容。 +│ ├───pages +│ └───Index.ets // 主界面。 +└───entry/src/main/resources // 资源目录。 +``` +### 具体实现 + +### 使用 AudioSession 管理应用音频焦点 +- 源码参考:[Index.ets](entry/src/main/ets/pages/Index.ets) +- 使用流程: + - 点击'设置场景'按钮,调用`audioSessionManager.setAudioSessionScene`设置当前音频场景为`MEDIA`。 + - 点击'激活焦点+注册监听'按钮,首先配置焦点策略为`CONCURRENCY_MIX_WITH_OTHERS`,接着调用`audioSessionManager.activateAudioSession`激活音频焦点,然后通过`audioSessionManager.on`监听焦点变化事件与焦点注销事件,监听事件触发后回调内容在回调信息栏打印。 + - 点击'判断焦点是否激活'按钮,调用`audioSessionManager.isAudioSessionActivated`来查询当前音频焦点激活状态,查询结果在运行结果栏打印。 + - 点击'注销焦点+注销监听'按钮,首先调用`audioSessionManager.deactivateAudioSession`停用当前应用的音频会话,再调用`audioSessionManager.off`来注销对焦点变化事件与焦点注销事件的监听。 + + +## 相关权限 + +不涉及。 + +## 模块依赖 + +不涉及。 + +## 约束与限制 + +1. 本示例支持在标准系统上运行,支持设备:RK3568。 + +2. 本示例支持API version 20,版本号: 6.0.0.43。 + +3. 本示例已支持使Build Version: 6.0.0.43, built on August 24, 2025。 + +4. 高等级APL特殊签名说明:无。 \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/build-profile.json5 b/MediaKit/AudioKit/AudioSessionSampleJS/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..3b9a7bcc04b86b38badd941e5a1109732f2ce493 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/build-profile.json5 @@ -0,0 +1,56 @@ +/* +* 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": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "targetSdkVersion": "6.0.0(20)", + "compatibleSdkVersion": "6.0.0(20)", + "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/MediaKit/AudioKit/AudioSessionSampleJS/code-linter.json5 b/MediaKit/AudioKit/AudioSessionSampleJS/code-linter.json5 new file mode 100644 index 0000000000000000000000000000000000000000..62baae473144070aa0fc532fce773652507047a1 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/code-linter.json5 @@ -0,0 +1,46 @@ +/* +* 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": { + "@security/no-unsafe-aes": "error", + "@security/no-unsafe-hash": "error", + "@security/no-unsafe-mac": "warn", + "@security/no-unsafe-dh": "error", + "@security/no-unsafe-dsa": "error", + "@security/no-unsafe-ecdsa": "error", + "@security/no-unsafe-rsa-encrypt": "error", + "@security/no-unsafe-rsa-sign": "error", + "@security/no-unsafe-rsa-key": "error", + "@security/no-unsafe-dsa-key": "error", + "@security/no-unsafe-dh-key": "error", + "@security/no-unsafe-3des": "error" + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/.gitignore b/MediaKit/AudioKit/AudioSessionSampleJS/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/build-profile.json5 b/MediaKit/AudioKit/AudioSessionSampleJS/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..2ded9f2f9d44b4d00eb8dbe3fccae05dde77acb5 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/build-profile.json5 @@ -0,0 +1,47 @@ +/* +* 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": { + "resOptions": { + "copyCodeResource": { + "enable": false + } + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/hvigorfile.ts b/MediaKit/AudioKit/AudioSessionSampleJS/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..61cca58be096424d17b6aadf3dff1c2ea06e05f3 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/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. */ +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/obfuscation-rules.txt b/MediaKit/AudioKit/AudioSessionSampleJS/entry/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/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/MediaKit/AudioKit/AudioSessionSampleJS/entry/oh-package.json5 b/MediaKit/AudioKit/AudioSessionSampleJS/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..e3d2e18789ceb3234d777badfa2f7da53be0e238 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/oh-package.json5 @@ -0,0 +1,27 @@ +/* +* 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": { + "@ohos/hypium": "1.0.15", + "@ohos/hamock": "1.0.0" + } +} + diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/ets/entryability/EntryAbility.ets b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..4ae1807cdc1b76c6ef0123c184f575f74b21b8f6 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,62 @@ +/* +* 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 { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +const DOMAIN = 0x0000; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + try { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + } catch (err) { + hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err)); + } + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); + return; + } + hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..96164e538b152554e43e37a07d8ff4e7db326a0d --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,30 @@ +/* +* 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'; + +const DOMAIN = 0x0000; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(DOMAIN, 'testTag', 'onBackup ok'); + await Promise.resolve(); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + await Promise.resolve(); + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/ets/pages/Index.ets b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..36404ef1f2a0e55d7bc70e213a1cc419d9658625 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,352 @@ +/* +* 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. +*/ + +// [Start all_focusprocess] +import { audio } from '@kit.AudioKit'; // 导入audio模块。 +import { BusinessError } from '@kit.BasicServicesKit'; // 导入BusinessError。 + +// [StartExclude all_focusprocess] +// 全局UI更新回调 +let globalLogUpdate: ((msg: string, isError: boolean) => void) | undefined; +let globalCallbackUpdate: ((msg: string) => void) | undefined; +// [EndExclude all_focusprocess] + +let audioSessionStateChangedCallback = (audioSessionStateChangedEvent: audio.AudioSessionStateChangedEvent) => { + console.info(`hint of audioSessionStateChanged: ${audioSessionStateChangedEvent.stateChangeHint} `); + + // [StartExclude all_focusprocess] + let callbackMsg = `hint of audioSessionStateChanged: ${audioSessionStateChangedEvent.stateChangeHint}`; + if (globalCallbackUpdate) { + globalCallbackUpdate(callbackMsg); + } + // [EndExclude all_focusprocess] + + switch (audioSessionStateChangedEvent.stateChangeHint) { + case audio.AudioSessionStateChangeHint.AUDIO_SESSION_STATE_CHANGE_HINT_PAUSE: + // 此分支表示系统已将音频流暂停,应用需切换至音频暂停状态。 + // 临时失去焦点:其他音频流释放音频焦点后,本音频流会收到resume事件,可继续播放。 + break; + case audio.AudioSessionStateChangeHint.AUDIO_SESSION_STATE_CHANGE_HINT_RESUME: + // 此分支表示系统解除AudioSession焦点的暂停操作。 + break; + case audio.AudioSessionStateChangeHint.AUDIO_SESSION_STATE_CHANGE_HINT_STOP: + // 此分支表示系统已将音频流停止(永久失去焦点),为保持状态一致,应用需切换至音频暂停状态。 + // 永久失去焦点:后续不会再收到音频焦点事件,恢复播放需用户主动触发。 + break; + case audio.AudioSessionStateChangeHint.AUDIO_SESSION_STATE_CHANGE_HINT_TIME_OUT_STOP: + // 此分支表示由于长时间无音频流播放,系统已将AudioSession停止(永久失去焦点),应用需切换至音频暂停状态。 + // 永久失去焦点:后续不会再收到音频焦点事件,恢复播放需用户主动触发。 + break; + case audio.AudioSessionStateChangeHint.AUDIO_SESSION_STATE_CHANGE_HINT_DUCK: + // 此分支表示系统已将音频音量降低(默认降到正常音量的20%)。 + break; + case audio.AudioSessionStateChangeHint.AUDIO_SESSION_STATE_CHANGE_HINT_UNDUCK: + // 此分支表示系统已将音频音量恢复正常。 + break; + default: + break; + } +}; + +// [Start all_sessionprocess] +// [Start get_sessionmanager] +let audioManager = audio.getAudioManager(); +let audioSessionManager: audio.AudioSessionManager = audioManager.getSessionManager(); +// [StartExclude all_sessionprocess] +// [End get_sessionmanager] +// [StartExclude all_focusprocess] +async function setAudioScene(){ + // 示例中选择了AUDIO_SESSION_SCENE_MEDIA会话场景,实际情况请根据具体场景修改该参数。 + // [EndExclude all_focusprocess] + // [Start set_audioscene] + audioSessionManager.setAudioSessionScene(audio.AudioSessionScene.AUDIO_SESSION_SCENE_MEDIA); + // [StartExclude set_audioscene] + // [StartExclude all_focusprocess] + console.info('Succeeded in doing activateAudioSession.'); + if (globalLogUpdate) { + globalLogUpdate('Succeeded in doing activateAudioSession.', false); + } +} + +async function activeSession(){ + // [EndExclude all_focusprocess] + // 示例中选择了CONCURRENCY_MIX_WITH_OTHERS策略,请根据具体场景修改该参数。 + // [Start active_audiosession] + // [EndExclude set_audioscene] + // [EndExclude all_sessionprocess] + let strategy: audio.AudioSessionStrategy = { + concurrencyMode: audio.AudioConcurrencyMode.CONCURRENCY_MIX_WITH_OTHERS + }; + + // 激活AudioSession,即抢占焦点 + audioSessionManager.activateAudioSession(strategy).then(() => { + console.info('Succeeded in doing activateAudioSession.'); + // [StartExclude active_audiosession] + if (globalLogUpdate) { + globalLogUpdate('Succeeded in doing activateAudioSession.', false); + } + // [EndExclude active_audiosession] + }).catch((err: BusinessError) => { + console.error(`Failed to activateAudioSession. Code: ${err.code}, message: ${err.message}`); + // [StartExclude active_audiosession] + if (globalLogUpdate) { + globalLogUpdate(`Failed to activateAudioSession. Code: ${err.code}, message: ${err.message}`, true); + } + // [EndExclude active_audiosession] + }); + // [End set_audioscene] + // [End active_audiosession] + let isActivated = audioSessionManager.isAudioSessionActivated(); + // [StartExclude all_sessionprocess] + if (!isActivated) { + console.error(`session is not activated.`); + } else { + console.info('session is activated.'); + } + + audioSessionManager.on('audioSessionStateChanged', audioSessionStateChangedCallback); + // [Start listen_audiosessiondeactiveevent] + // [EndExclude all_sessionprocess] + audioSessionManager.on('audioSessionDeactivated', (audioSessionDeactivatedEvent: audio + .AudioSessionDeactivatedEvent) => { + console.info(`reason of audioSessionDeactivated: ${audioSessionDeactivatedEvent.reason} `); + // [StartExclude listen_audiosessiondeactiveevent] + if (globalCallbackUpdate) { + globalCallbackUpdate(`reason of audioSessionDeactivated: ${audioSessionDeactivatedEvent.reason}`); + } + // [EndExclude listen_audiosessiondeactiveevent] + }); + // [StartExclude all_sessionprocess] + // [End listen_audiosessiondeactiveevent] +} +// 根据实际业务,可以启动多个AudioRenderer等音频播放。 +// [StartExclude all_focusprocess] +async function deactiveSession(){ + // [EndExclude all_sessionprocess] + // [EndExclude all_focusprocess] + // 结束AudioSession,即释放焦点 + audioSessionManager.deactivateAudioSession().then(() => { + console.info('Succeeded in doing deactivateAudioSession.'); + // [StartExclude all_focusprocess] + if (globalLogUpdate) { + globalLogUpdate('Succeeded in doing deactivateAudioSession.', false); + } + // [EndExclude all_focusprocess] + }).catch((err: BusinessError) => { + console.error(`Failed to deactivateAudioSession. Code: ${err.code}, message: ${err.message}`); + // [StartExclude all_focusprocess] + if (globalLogUpdate) { + globalLogUpdate(`Failed to deactivateAudioSession. Code: ${err.code}, message: ${err.message}`, true); + } + // [EndExclude all_focusprocess] + }); + // [StartExclude all_sessionprocess] + // [StartExclude all_focusprocess] + // [Start audiosession_checkisactivated] + let isActivated = audioSessionManager.isAudioSessionActivated(); + // [End audiosession_checkisactivated] + if (!isActivated){ + console.error(`deactive AudioSession successfully`); + } + // [EndExclude all_focusprocess] + audioSessionManager.off('audioSessionStateChanged', audioSessionStateChangedCallback); + // [End all_focusprocess] + // 停用音频会话 + // [Start cancellisten_audiosessiondeactiveevent] + // [EndExclude all_sessionprocess] + audioSessionManager.off('audioSessionDeactivated'); + // [End all_sessionprocess] + // [End cancellisten_audiosessiondeactiveevent] +} +async function Sessionisactivated(){ + // [Start audiosession_checkisactivated] + let isActivated = audioSessionManager.isAudioSessionActivated(); + // [End audiosession_checkisactivated] + if (!isActivated) { + console.error(`session is not activated.`); + } else { + console.info('session is activated.'); + } +} + +@Entry +@Component +struct Index { + @State private runningResult: string = '暂无运行结果'; + @State logMessages: string = '暂无日志信息'; + @State callbackMessages: string = '暂无回调信息'; + + aboutToAppear(): void { + // 设置全局回调 + globalLogUpdate = (msg, isError) => this.updateLogInfo(msg, isError); + globalCallbackUpdate = (msg) => this.updateCallbackInfo(msg); + } + + // 更新日志信息 + updateLogInfo(msg: string, isError: boolean): void { + const timestamp = new Date().toLocaleTimeString(); + const prefix = isError ? '[ERROR]' : '[INFO]'; + this.logMessages = `[${timestamp}] ${prefix} ${msg}`; + } + + // 更新回调信息 + updateCallbackInfo(msg: string): void { + const timestamp = new Date().toLocaleTimeString(); + this.callbackMessages = `[${timestamp}] ${msg}`; + } + + build(): void { + Scroll() { + Column() { + // 信息显示区域 + Column() { + Text('实时状态信息') + .fontSize(18) + .fontWeight(600) + .margin({ bottom: 12 }) + + // 运行结果 + Column() { + Text('运行结果') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.runningResult) + .fontSize(12) + .fontColor('#007DFF') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 日志信息 + Column() { + Text('日志信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.logMessages) + .fontSize(12) + .fontColor('#52C41A') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 回调信息 + Column() { + Text('回调信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.callbackMessages) + .fontSize(12) + .fontColor('#FA8C16') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 20 }) + } + .width('100%') + .padding(16) + .backgroundColor(Color.White) + .borderRadius(12) + .margin({ bottom: 16 }) + + // 功能按钮 + Row() { + Button() { + Text('设置场景').fontSize(15).fontColor(Color.White) + } + .width('80%') + .height(40) + .onClick(async (): Promise => { + await setAudioScene() + this.runningResult = "set session scene:MEDIA" + }) + } + .width('80%') + .height(50) + .justifyContent(FlexAlign.Center) + + Row() { + Button() { + Text('激活焦点+注册监听').fontSize(15).fontColor(Color.White) + } + .width('80%') + .height(40) + .onClick(async (): Promise => { + await activeSession() + this.runningResult = "active session with strategy MIX WITH OTHERS" + }) + } + .width('80%') + .height(50) + .justifyContent(FlexAlign.Center) + + Row() { + Button() { + Text('判断焦点是否激活').fontSize(15).fontColor(Color.White) + } + .width('80%') + .height(40) + .onClick(async (): Promise => { + await Sessionisactivated() + this.runningResult = "active session with strategy PAUSE_OTHERS" + }) + } + .width('80%') + .height(50) + .justifyContent(FlexAlign.Center) + + Row() { + Button() { + Text('注销焦点+注销监听').fontSize(15).fontColor(Color.White) + } + .width('80%') + .height(40) + .onClick(async (): Promise => { + await deactiveSession() + this.runningResult = "deactive session success" + }) + } + .width('80%') + .height(50) + .justifyContent(FlexAlign.Center) + } + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5'); + } + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/module.json5 b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..e11e57fba9ebc4a158b0fd0fc93c54cfb021b4b9 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/module.json5 @@ -0,0 +1,64 @@ +/* +* 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": [ + "default" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "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": [ + "ohos.want.action.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ], + } + ] + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/element/color.json b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/element/float.json b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..33ea22304f9b1485b5f22d811023701b5d4e35b6 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/element/float.json @@ -0,0 +1,8 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + } + ] +} diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/element/string.json b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f94595515a99e0c828807e243494f57f09251930 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/media/background.png b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/media/background.png differ diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/media/foreground.png b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97014d3e10e5ff511409c378cd4255713aecd85f Binary files /dev/null and b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/media/foreground.png differ diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/media/layered_image.json b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/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/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/media/startIcon.png b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/media/startIcon.png differ diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/profile/backup_config.json b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/profile/main_pages.json b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..1898d94f58d6128ab712be2c68acc7c98e9ab9ce --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,5 @@ +{ + "src": [ + "pages/Index" + ] +} diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/dark/element/color.json b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/main/resources/dark/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..79b11c2747aec33e710fd3a7b2b3c94dd9965499 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/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/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..621da0d393a21467d95052cf350dd15f438996e8 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,49 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/ohosTest/ets/test/List.test.ets b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..74e0080a822b473ab96adbf15bce79f520f5dbde --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,21 @@ +/* +* 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 abilityTest from './Ability.test'; +import AudioSessionManagerTest from './avsession.test'; + +export default function testsuite() { + abilityTest(); + AudioSessionManagerTest() +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/ohosTest/ets/test/avsession.test.ets b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/ohosTest/ets/test/avsession.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..e0ad48f6bcbc794ae993234264185e11243fa534 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/ohosTest/ets/test/avsession.test.ets @@ -0,0 +1,134 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, Level } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const TAG = '[VoIPDemoForAudioSession]'; +const DOMAIN = 0xF81A; +const BUNDLE = 'VoIPDemoForAudioSession_'; +let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const driver = Driver.create(); + +async function runTest( + testCaseName: string, + buttonText: string, + delayTime: number, + description: string, + done: Function +) { + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, begin`); + + let btn = await driver.findComponent(ON.text(buttonText)); + if (btn !== undefined) { + await btn.click(); + await driver.delayMs(delayTime); + } else { + hilog.warn(DOMAIN, TAG, BUNDLE + `Button "${buttonText}" not found.`); + await driver.delayMs(delayTime); + } + + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, end`); + done(); +} + +export default function VoIPDemoForAudioSessionTest() { + describe('VoIPDemoForAudioSessionTest', () => { + beforeAll(() => { + abilityDelegator.startAbility({ + bundleName: 'com.example.myapplication', + abilityName: 'EntryAbility' + }); + }); + + beforeEach(() => {}) + afterEach(() => {}) + afterAll(() => {}) + + /** + * @tc.number VoIPDemoForAudioSession_SetScene_0001 + * @tc.name VoIPDemoForAudioSession_SetScene_0001 + * @tc.desc Set audio session scene to MEDIA via UI action + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioSession_SetScene_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioSession_SetScene_0001', + '设置场景', + 3000, + 'Set AudioSession scene to MEDIA', + done + ); + }); + + /** + * @tc.number VoIPDemoForAudioSession_ActivateSession_0001 + * @tc.name VoIPDemoForAudioSession_ActivateSession_0001 + * @tc.desc Activate audio session with CONCURRENCY_MIX_WITH_OTHERS strategy and register state listeners + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioSession_ActivateSession_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioSession_ActivateSession_0001', + '激活焦点+注册监听', + 4000, + 'Activate AudioSession with MIX_WITH_OTHERS concurrency mode and register listeners', + done + ); + }); + + /** + * @tc.number VoIPDemoForAudioSession_CheckActivated_0001 + * @tc.name VoIPDemoForAudioSession_CheckActivated_0001 + * @tc.desc Verify whether the audio session is currently activated + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioSession_CheckActivated_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioSession_CheckActivated_0001', + '判断焦点是否激活', + 2000, + 'Check AudioSession activation status using isAudioSessionActivated API', + done + ); + }); + + /** + * @tc.number VoIPDemoForAudioSession_DeactivateSession_0001 + * @tc.name VoIPDemoForAudioSession_DeactivateSession_0001 + * @tc.desc Deactivate audio session and unregister all event listeners + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioSession_DeactivateSession_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioSession_DeactivateSession_0001', + '注销焦点+注销监听', + 3000, + 'Deactivate AudioSession and remove all registered event listeners', + done + ); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/ohosTest/module.json5 b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..0a099ee4bd05a001f943915cd4eb2e01aabeaca0 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/ohosTest/module.json5 @@ -0,0 +1,25 @@ +/* +* 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_test", + "type": "feature", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/test/List.test.ets b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..c2359615fdfb661f743a2cac5886892209c7aee6 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/test/List.test.ets @@ -0,0 +1,19 @@ +/* +* 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 localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/test/LocalUnit.test.ets b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..45ca114e698503447e7ccc80364c54cf7fa50f46 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,47 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/hvigor/hvigor-config.json5 b/MediaKit/AudioKit/AudioSessionSampleJS/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7d7d5f8aa0d0c54d1c8daf78625b9b3bbf52aee8 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/hvigor/hvigor-config.json5 @@ -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. +*/ +{ + "modelVersion": "6.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | 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 */ + // "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */ + }, + "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/MediaKit/AudioKit/AudioSessionSampleJS/hvigorfile.ts b/MediaKit/AudioKit/AudioSessionSampleJS/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c5b9939f0c562551a58ebd26abae33caf4f5e28 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/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. */ +} \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/oh-package.json5 b/MediaKit/AudioKit/AudioSessionSampleJS/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..d4c67d9bed2040005240f4f66a6601227e30809b --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/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": "6.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.24", + "@ohos/hamock": "1.0.0" + } +} diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/ohosTest.md b/MediaKit/AudioKit/AudioSessionSampleJS/ohosTest.md new file mode 100644 index 0000000000000000000000000000000000000000..775b902adb2351c785d468403e41a0f2a87e58f3 --- /dev/null +++ b/MediaKit/AudioKit/AudioSessionSampleJS/ohosTest.md @@ -0,0 +1,11 @@ +# 音频焦点和音频会话测试用例归档 + +## 用例表 + +| 测试功能 | 预置条件 | 输入 | 预期输出 | 是否自动 | +|----------------------------|----------|----------------------|------------------------------------------------------|---------| +| 拉起应用 | 设备正常运行 | - | 成功拉起应用,进入首页 | 是 | +| 设置音频会话场景 | 位于主页 | 点击"设置场景" | 成功将 AudioSession 场景设为 MEDIA | 是 | +| 激活音频会话并注册监听 | 位于主页 | 点击"激活焦点+注册监听" | 激活 AudioSession(并发策略为 MIX_WITH_OTHERS)并注册状态监听器 | 是 | +| 判断音频会话是否激活 | 位于主页 | 点击"判断焦点是否激活" | 调用 isAudioSessionActivated 接口,返回当前激活状态(true/false) | 是 | +| 注销音频会话并注销监听 | 位于主页 | 点击"注销焦点+注销监听" | 停用 AudioSession 并移除所有已注册的事件监听器 | 是 | \ No newline at end of file diff --git a/MediaKit/AudioKit/AudioSessionSampleJS/screenshots/focus_js.png b/MediaKit/AudioKit/AudioSessionSampleJS/screenshots/focus_js.png new file mode 100644 index 0000000000000000000000000000000000000000..e07f3c00fa2c497c5925f902ec10877778056d4d Binary files /dev/null and b/MediaKit/AudioKit/AudioSessionSampleJS/screenshots/focus_js.png differ diff --git a/MediaKit/AudioKit/VoipCallSampleJS/.gitignore b/MediaKit/AudioKit/VoipCallSampleJS/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..d2ff20141ceed86d87c0ea5d99481973005bab2b --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/.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/MediaKit/AudioKit/VoipCallSampleJS/AppScope/app.json5 b/MediaKit/AudioKit/VoipCallSampleJS/AppScope/app.json5 new file mode 100644 index 0000000000000000000000000000000000000000..02e14fba2e646d86166447488188b104168fecbc --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/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.myapplication", + "vendor": "example", + "versionCode": 1000000, + "versionName": "1.0.0", + "icon": "$media:layered_image", + "label": "$string:app_name" + } +} diff --git a/MediaKit/AudioKit/VoipCallSampleJS/AppScope/resources/base/element/string.json b/MediaKit/AudioKit/VoipCallSampleJS/AppScope/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..1080233f01384411ec684b58955cb8808746fdd3 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/AppScope/resources/base/element/string.json @@ -0,0 +1,8 @@ +{ + "string": [ + { + "name": "app_name", + "value": "MyApplication" + } + ] +} diff --git a/MediaKit/AudioKit/VoipCallSampleJS/AppScope/resources/base/media/background.png b/MediaKit/AudioKit/VoipCallSampleJS/AppScope/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/MediaKit/AudioKit/VoipCallSampleJS/AppScope/resources/base/media/background.png differ diff --git a/MediaKit/AudioKit/VoipCallSampleJS/AppScope/resources/base/media/foreground.png b/MediaKit/AudioKit/VoipCallSampleJS/AppScope/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..eb9427585b36d14b12477435b6419d1f07b3e0bb Binary files /dev/null and b/MediaKit/AudioKit/VoipCallSampleJS/AppScope/resources/base/media/foreground.png differ diff --git a/MediaKit/AudioKit/VoipCallSampleJS/AppScope/resources/base/media/layered_image.json b/MediaKit/AudioKit/VoipCallSampleJS/AppScope/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/AppScope/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/MediaKit/AudioKit/VoipCallSampleJS/README.md b/MediaKit/AudioKit/VoipCallSampleJS/README.md new file mode 100644 index 0000000000000000000000000000000000000000..51dc6b2c15f9f4e498456cb20b960fbf4dab3151 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/README.md @@ -0,0 +1,77 @@ +# 音频通话功能示例 + +## 介绍 + +本示例基于AudioRender与AudioCapture能力,实现了语音通话场景,包含语音流的录制与播放的全流程。 + +## 效果图预览 + +**图1**:首页 + +选择跳转到对应功能页面。 + + + +**图2**:播放对端通话声音界面 + +- 依次点击'初始化'、'开始播放'按钮,即可创建语音通话流播放音频。 +- 点击'暂停播放'按钮,即可暂停音频播放,可以通过点击'开始播放'来恢复音频播放。 +- 点击'停止播放'、'释放资源'按钮,即可结束音频播放。 + + + +**图3**:录制本端通话声音界面 + +- 依次点击'初始化'、'开始录制'按钮,即可创建语音通话流录制音频。 +- 点击'停止录制'、'释放资源'按钮,即可结束录制。 + + + +## 工程结构&模块类型 + +``` +├───entry/src/main/ets +│ ├───pages +│ │ └───Index.ets // 首页。 +│ │ └───VoIpDemoForAudioCapturer.ets // 使用AudioCapturer录制对端的通话声音页。 +│ │ └───VoIpDemoForAudioRenderer.ets // 使用AudioRenderer播放本端的通话声音页。 +└───entry/src/main/resources // 资源目录。 +``` + +## 具体实现 + +### 使用AudioRenderer播放对端的通话声音 +- 源码参考:[VoIpDemoForAudioRenderer.ets](entry/src/main/ets/pages/VoIpDemoForAudioRenderer.ets) +- 使用流程: + - 点击'初始化'按钮,开始配置`audioRendererOptions`内容,包括采样率、通道数、采样格式、编码格式、流类型以及采集器标志,其中流类型设置为`STREAM_USAGE_VOICE_COMMUNICATION`即可创建语音通话类型的播放流。接着配置写入数据回调并订阅监听。最后调用`audio.createAudioRenderer`创建播放实例。 + - 点击'开始播放'按钮,调用`audioRenderer.start`,开始播放。 + - 点击'暂停播放'按钮,调用`audioRenderer.pause`,暂停录制。 + - 点击'停止播放'按钮,调用`audioRenderer.stop`,停止播放。 + - 点击'释放资源'按钮,调用`audioRenderer.release`,释放音频流资源并注销回调。 + +### 使用AudioCapturer录制本端的通话声音 +- 源码参考:[VoIpDemoForAudioCapturer.ets](entry/src/main/ets/pages/VoIpDemoForAudioCapturer.ets) +- 使用流程: + - 需申请`ohos.permission.MICROPHONE`权限来获取麦克风权限保证录音可以正常起流。当获取权限成功后,后续再使用麦克风权限的接口时不需要重复申请该权限。 + - 点击'初始化'按钮,开始配置`AudioCapturerOptions`内容,包括采样率、通道数、采样格式、编码格式、音源类型以及采集器标志,其中音源类型设置为`SOURCE_TYPE_VOICE_COMMUNICATION`即可创建语音通话类型的录制流。接着配置读入数据回调并订阅监听。最后调用`audio.createAudioCapturer`创建录制实例。 + - 点击'开始录制'按钮,调用`audioCapturer.start`,开始录制。 + - 点击'停止录制'按钮,调用`audioCapturer.stop`,停止录制。 + - 点击'释放资源'按钮,调用`audioCapturer.release`,释放音频流资源并注销回调。 + +## 相关权限 + +麦克风使用权限:ohos.permission.MICROPHONE + +## 模块依赖 + +不涉及。 + +## 约束与限制 + +1. 本示例支持在标准系统上运行,支持设备:RK3568。 + +2. 本示例支持API version 20,版本号: 6.0.0.43。 + +3. 本示例已支持使Build Version: 6.0.0.43, built on August 24, 2025。 + +4. 高等级APL特殊签名说明:无。 \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/build-profile.json5 b/MediaKit/AudioKit/VoipCallSampleJS/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..3b9a7bcc04b86b38badd941e5a1109732f2ce493 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/build-profile.json5 @@ -0,0 +1,56 @@ +/* +* 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": [], + "products": [ + { + "name": "default", + "signingConfig": "default", + "targetSdkVersion": "6.0.0(20)", + "compatibleSdkVersion": "6.0.0(20)", + "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/MediaKit/AudioKit/VoipCallSampleJS/code-linter.json5 b/MediaKit/AudioKit/VoipCallSampleJS/code-linter.json5 new file mode 100644 index 0000000000000000000000000000000000000000..62baae473144070aa0fc532fce773652507047a1 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/code-linter.json5 @@ -0,0 +1,46 @@ +/* +* 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": { + "@security/no-unsafe-aes": "error", + "@security/no-unsafe-hash": "error", + "@security/no-unsafe-mac": "warn", + "@security/no-unsafe-dh": "error", + "@security/no-unsafe-dsa": "error", + "@security/no-unsafe-ecdsa": "error", + "@security/no-unsafe-rsa-encrypt": "error", + "@security/no-unsafe-rsa-sign": "error", + "@security/no-unsafe-rsa-key": "error", + "@security/no-unsafe-dsa-key": "error", + "@security/no-unsafe-dh-key": "error", + "@security/no-unsafe-3des": "error" + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/.gitignore b/MediaKit/AudioKit/VoipCallSampleJS/entry/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..e2713a2779c5a3e0eb879efe6115455592caeea5 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/oh_modules +/.preview +/build +/.cxx +/.test \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/build-profile.json5 b/MediaKit/AudioKit/VoipCallSampleJS/entry/build-profile.json5 new file mode 100644 index 0000000000000000000000000000000000000000..2ded9f2f9d44b4d00eb8dbe3fccae05dde77acb5 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/build-profile.json5 @@ -0,0 +1,47 @@ +/* +* 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": { + "resOptions": { + "copyCodeResource": { + "enable": false + } + } + }, + "buildOptionSet": [ + { + "name": "release", + "arkOptions": { + "obfuscation": { + "ruleOptions": { + "enable": false, + "files": [ + "./obfuscation-rules.txt" + ] + } + } + } + }, + ], + "targets": [ + { + "name": "default" + }, + { + "name": "ohosTest", + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/hvigorfile.ts b/MediaKit/AudioKit/VoipCallSampleJS/entry/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..61cca58be096424d17b6aadf3dff1c2ea06e05f3 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/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. */ +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/obfuscation-rules.txt b/MediaKit/AudioKit/VoipCallSampleJS/entry/obfuscation-rules.txt new file mode 100644 index 0000000000000000000000000000000000000000..272efb6ca3f240859091bbbfc7c5802d52793b0b --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/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/MediaKit/AudioKit/VoipCallSampleJS/entry/oh-package.json5 b/MediaKit/AudioKit/VoipCallSampleJS/entry/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..cef04b2310ef09b8eaf16440e01c090501a79438 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/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": {} +} + diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/entryability/EntryAbility.ets b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/entryability/EntryAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..4ae1807cdc1b76c6ef0123c184f575f74b21b8f6 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/entryability/EntryAbility.ets @@ -0,0 +1,62 @@ +/* +* 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 { AbilityConstant, ConfigurationConstant, UIAbility, Want } from '@kit.AbilityKit'; +import { hilog } from '@kit.PerformanceAnalysisKit'; +import { window } from '@kit.ArkUI'; + +const DOMAIN = 0x0000; + +export default class EntryAbility extends UIAbility { + onCreate(want: Want, launchParam: AbilityConstant.LaunchParam): void { + try { + this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET); + } catch (err) { + hilog.error(DOMAIN, 'testTag', 'Failed to set colorMode. Cause: %{public}s', JSON.stringify(err)); + } + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onCreate'); + } + + onDestroy(): void { + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onDestroy'); + } + + onWindowStageCreate(windowStage: window.WindowStage): void { + // Main window is created, set main page for this ability + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageCreate'); + + windowStage.loadContent('pages/Index', (err) => { + if (err.code) { + hilog.error(DOMAIN, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err)); + return; + } + hilog.info(DOMAIN, 'testTag', 'Succeeded in loading the content.'); + }); + } + + onWindowStageDestroy(): void { + // Main window is destroyed, release UI related resources + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onWindowStageDestroy'); + } + + onForeground(): void { + // Ability has brought to foreground + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onForeground'); + } + + onBackground(): void { + // Ability has back to background + hilog.info(DOMAIN, 'testTag', '%{public}s', 'Ability onBackground'); + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets new file mode 100644 index 0000000000000000000000000000000000000000..96164e538b152554e43e37a07d8ff4e7db326a0d --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/entrybackupability/EntryBackupAbility.ets @@ -0,0 +1,30 @@ +/* +* 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'; + +const DOMAIN = 0x0000; + +export default class EntryBackupAbility extends BackupExtensionAbility { + async onBackup() { + hilog.info(DOMAIN, 'testTag', 'onBackup ok'); + await Promise.resolve(); + } + + async onRestore(bundleVersion: BundleVersion) { + hilog.info(DOMAIN, 'testTag', 'onRestore ok %{public}s', JSON.stringify(bundleVersion)); + await Promise.resolve(); + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/pages/Index.ets b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/pages/Index.ets new file mode 100644 index 0000000000000000000000000000000000000000..2b1c711eb89b54d81af498806f750c23b06e1a47 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/pages/Index.ets @@ -0,0 +1,54 @@ +/* +* 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 {Router} from '@ohos.arkui.UIContext'; +const router = new Router(); + +@Entry +@Component +struct Index { + build() { + Column() { + Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center}) { + this.textComponent('Index'); + this.buttonComponent('使用AudioCapturer录制本端的通话声音页面', 'pages/VoIpDemoForAudioCapturer'); + this.buttonComponent('使用AudioRenderer播放对端的通话声音页面', 'pages/VoIpDemoForAudioRenderer'); + }.size({ width: '100%', height: '100%' }) + } + .width('100%') + .height('100%') + .justifyContent(FlexAlign.Center) + .alignItems(HorizontalAlign.Center) + } + + @Builder + textComponent(name: string) { + Text(name) + .textAlign(TextAlign.Center) + .margin(10) + .fontSize($r('app.float.index_text_font_size')) + .size({ width: '100%', height: $r('app.float.index_button_height_size') }) + } + + @Builder + buttonComponent(name: string, dstUri: string) { + Button(name) + .width('50%') + .margin(10) + .onClick((e: ClickEvent) => { + router.pushUrl({ url: dstUri }); + }) + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/pages/VoIpDemoForAudioCapturer.ets b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/pages/VoIpDemoForAudioCapturer.ets new file mode 100644 index 0000000000000000000000000000000000000000..afa4b58e450b62edc3e68971412ceb3b12e3631a --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/pages/VoIpDemoForAudioCapturer.ets @@ -0,0 +1,420 @@ +/* +* 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. +*/ + +// [Start all_VoIPDemoForAudioCapturer] +import { audio } from '@kit.AudioKit'; // 导入audio模块。 +import { BusinessError } from '@kit.BasicServicesKit'; // 导入BusinessError。 +import { fileIo as fs } from '@kit.CoreFileKit'; // 导入文件操作模块。 +import { common, abilityAccessCtrl, PermissionRequestResult } from '@kit.AbilityKit'; // 导入UIAbilityContext。 +// 与使用AudioCapturer开发音频录制功能过程相似,关键区别在于audioCapturerInfo参数和音频数据流向。 +const TAG = 'VoIPDemoForAudioCapturer'; + +class Options { + offset?: number; + length?: number; +} + +let bufferSize: number = 0; +let audioCapturer: audio.AudioCapturer | undefined = undefined; +let audioStreamInfo: audio.AudioStreamInfo = { + samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率。 + channels: audio.AudioChannel.CHANNEL_2, // 通道。 + sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式。 + encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式。 +}; +let audioCapturerInfo: audio.AudioCapturerInfo = { + // 需使用通话场景相应的参数。1 + source: audio.SourceType.SOURCE_TYPE_VOICE_COMMUNICATION, // 音源类型:语音通话。 + capturerFlags: 0 // 音频采集器标志:默认为0即可。 +}; +let audioCapturerOptions: audio.AudioCapturerOptions = { + streamInfo: audioStreamInfo, + capturerInfo: audioCapturerInfo +}; +let file: fs.File; +let readDataCallback: Callback; + +// [StartExclude all_VoIPDemoForAudioCapturer] +// 全局UI更新回调 +let globalLogUpdate: ((msg: string, isError: boolean) => void) | undefined; +let globalCallbackUpdate: ((msg: string) => void) | undefined; + +async function requestMicrophonePermission(context: common.UIAbilityContext): Promise { + let atManager = abilityAccessCtrl.createAtManager(); + let result: PermissionRequestResult = await atManager.requestPermissionsFromUser(context, ['ohos.permission.MICROPHONE']); + const hasPermission = result.authResults[0] === 0; + + if (hasPermission) { + console.info('麦克风权限已授予'); + if (globalLogUpdate) { + globalLogUpdate('麦克风权限已授予', false); + } + } else { + console.error('麦克风权限未授权,无法录音'); + if (globalLogUpdate) { + globalLogUpdate('麦克风权限未授权,无法录音', true); + } + } + + return hasPermission; +} +// [EndExclude all_VoIPDemoForAudioCapturer] + +async function initArguments(context: common.UIAbilityContext) { + let path = context.cacheDir; + let filePath = path + '/StarWars10s-2C-48000-4SW.pcm'; + file = fs.openSync(filePath, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE); + console.info(`File opened: ${filePath}`); + + readDataCallback = (buffer: ArrayBuffer) => { + let options: Options = { + offset: bufferSize, + length: buffer.byteLength + } + fs.writeSync(file.fd, buffer, options); + bufferSize += buffer.byteLength; + } +} + +// 初始化,创建实例,设置监听事件。 +async function init() { + audio.createAudioCapturer(audioCapturerOptions, (err, capturer) => { // 创建AudioCapturer实例。 + if (err) { + console.error(`Invoke createAudioCapturer failed, code is ${err.code}, message is ${err.message}`); + // [StartExclude all_VoIPDemoForAudioCapturer] + const errorMsg = `Invoke createAudioCapturer failed, code is ${err.code}, message is ${err.message}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude all_VoIPDemoForAudioCapturer] + return; + } + console.info(`${TAG}: create AudioCapturer success`); + // [StartExclude all_VoIPDemoForAudioCapturer] + const successMsg = `${TAG}: create AudioCapturer success`; + if (globalLogUpdate) { + globalLogUpdate(successMsg, false); + } + // [EndExclude all_VoIPDemoForAudioCapturer] + audioCapturer = capturer; + if (audioCapturer !== undefined) { + audioCapturer.on('readData', readDataCallback); + } + }); +} + +// 开始一次音频采集。 +async function start() { + if (audioCapturer !== undefined) { + let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED]; + if (stateGroup.indexOf(audioCapturer.state.valueOf()) === -1) { // 当且仅当状态为STATE_PREPARED、STATE_PAUSED和STATE_STOPPED之一时才能启动采集。 + console.error(`${TAG}: start failed`); + // [StartExclude all_VoIPDemoForAudioCapturer] + const errorMsg = `${TAG}: start failed - Invalid state: ${audioCapturer.state}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude all_VoIPDemoForAudioCapturer] + return; + } + + // 启动采集。 + audioCapturer.start((err: BusinessError) => { + if (err) { + console.error('Capturer start failed.'); + // [StartExclude all_VoIPDemoForAudioCapturer] + const errorMsg = `Capturer start failed.`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude all_VoIPDemoForAudioCapturer] + } else { + console.info('Capturer start success.'); + // [StartExclude all_VoIPDemoForAudioCapturer] + if (globalLogUpdate) { + globalLogUpdate('Capturer start success.', false); + } + // [EndExclude all_VoIPDemoForAudioCapturer] + } + }); + } +} + +// 停止采集。 +async function stop() { + if (audioCapturer !== undefined) { + // 只有采集器状态为STATE_RUNNING或STATE_PAUSED的时候才可以停止。 + if (audioCapturer.state.valueOf() !== audio.AudioState.STATE_RUNNING + && audioCapturer.state.valueOf() !== audio.AudioState.STATE_PAUSED) { + console.info('Capturer is not running or paused'); + // [StartExclude all_VoIPDemoForAudioCapturer] + const infoMsg = `Capturer is not running or paused - Current state: ${audioCapturer.state}`; + if (globalLogUpdate) { + globalLogUpdate(infoMsg, false); + } + // [EndExclude all_VoIPDemoForAudioCapturer] + return; + } + + // 停止采集。 + audioCapturer.stop((err: BusinessError) => { + if (err) { + console.error('Capturer stop failed.'); + // [StartExclude all_VoIPDemoForAudioCapturer] + const errorMsg = `Capturer stop failed. Code: ${err.code}, message: ${err.message}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude all_VoIPDemoForAudioCapturer] + } else { + fs.close(file); + console.info('Capturer stop success.'); + // [StartExclude all_VoIPDemoForAudioCapturer] + const successMsg = `Capturer stop success.\nTotal recorded: ${bufferSize} bytes`; + if (globalLogUpdate) { + globalLogUpdate(successMsg, false); + } + // [EndExclude all_VoIPDemoForAudioCapturer] + } + }); + } +} + +// 销毁实例,释放资源。 +async function release() { + if (audioCapturer !== undefined) { + // 采集器状态不是STATE_RELEASED或STATE_NEW状态,才能release。 + if (audioCapturer.state.valueOf() === audio.AudioState.STATE_RELEASED || audioCapturer.state.valueOf() === audio.AudioState.STATE_NEW) { + console.info('Capturer already released'); + // [StartExclude all_VoIPDemoForAudioCapturer] + if (globalLogUpdate) { + globalLogUpdate('Capturer already released', false); + } + // [EndExclude all_VoIPDemoForAudioCapturer] + return; + } + + // 释放资源。 + audioCapturer.release((err: BusinessError) => { + if (err) { + console.error('Capturer release failed.'); + // [StartExclude all_VoIPDemoForAudioCapturer] + const errorMsg = `Capturer release failed.`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude all_VoIPDemoForAudioCapturer] + } else { + console.info('Capturer release success.'); + // [StartExclude all_VoIPDemoForAudioCapturer] + if (globalLogUpdate) { + globalLogUpdate('Capturer release success.', false); + } + // [EndExclude all_VoIPDemoForAudioCapturer] + } + }); + } +} +// [End all_VoIPDemoForAudioCapturer] + +@Entry +@Component +struct Index { + @State currentState: string = '未初始化'; + @State logMessages: string = '暂无日志信息'; + @State callbackMessages: string = '暂无回调信息'; + + aboutToAppear(): void { + // 设置全局回调 + globalLogUpdate = (msg, isError) => this.updateLogInfo(msg, isError); + globalCallbackUpdate = (msg) => this.updateCallbackInfo(msg); + } + + // 更新日志信息 + updateLogInfo(msg: string, isError: boolean): void { + const timestamp = new Date().toLocaleTimeString(); + const prefix = isError ? '[ERROR]' : '[INFO]'; + this.logMessages = `[${timestamp}] ${prefix} ${msg}`; + } + + // 更新回调信息 + updateCallbackInfo(msg: string): void { + const timestamp = new Date().toLocaleTimeString(); + this.callbackMessages = `[${timestamp}] ${msg}`; + } + + build(): void { + Scroll() { + Column() { + // 信息显示区域 + Column() { + Text('实时状态信息') + .fontSize(18) + .fontWeight(600) + .margin({ bottom: 12 }) + + // 当前状态 + Column() { + Text('当前状态') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.currentState) + .fontSize(14) + .fontColor('#007DFF') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 日志信息 + Column() { + Text('日志信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.logMessages) + .fontSize(12) + .fontColor('#52C41A') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 回调信息 + Column() { + Text('数据回调信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.callbackMessages) + .fontSize(12) + .fontColor('#FA8C16') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 20 }) + } + .width('100%') + .padding(16) + .backgroundColor(Color.White) + .borderRadius(12) + .margin({ bottom: 16 }) + + // 功能按钮 + Row() { + Column() { + Text('初始化').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 8, bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在初始化...'; + let context = this.getUIContext().getHostContext() as common.UIAbilityContext; + // [StartExclude all_VoIPDemoForAudioCapturer] + let hasPermission = await requestMicrophonePermission(context); + if (!hasPermission) { + this.currentState = '初始化失败:权限未授予'; + return; + } + // [EndExclude all_VoIPDemoForAudioCapturer] + await initArguments(context); + await init(); + this.currentState = '初始化完成'; + }); + + Column() { + Text('开始录制').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在开始录制...'; + await start(); + this.currentState = '录制中'; + }); + } + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + + Row() { + Column() { + Text('停止录制').fontColor(Color.Black).fontSize(14); + } + .id('audio_effect_manager_card') + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 8, bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在停止录制...'; + await stop(); + this.currentState = '已停止'; + }); + + Column() { + Text('释放资源').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在释放资源...'; + await release(); + this.currentState = '已释放'; + }); + } + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + } + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5') + .padding(16); + } + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/pages/VoIpDemoForAudioRenderer.ets b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/pages/VoIpDemoForAudioRenderer.ets new file mode 100644 index 0000000000000000000000000000000000000000..65f14ea9b7dd7b8cf72771a4cec8df566ca66b8a --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/ets/pages/VoIpDemoForAudioRenderer.ets @@ -0,0 +1,465 @@ +/* +* 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. +*/ + +// [Start all_VoIPDemoForAudioRenderer] +import { audio } from '@kit.AudioKit'; // 导入audio模块。 +import { BusinessError } from '@kit.BasicServicesKit'; // 导入BusinessError。 +import { fileIo as fs } from '@kit.CoreFileKit'; // 导入文件操作模块。 +import { common } from '@kit.AbilityKit'; // 导入UIAbilityContext。 + +// 与使用AudioRenderer开发音频播放功能过程相似,关键区别在于audioRendererInfo参数和音频数据来源。 +const TAG = 'VoIPDemoForAudioRenderer'; + +class Options { + offset?: number; + length?: number; +} + +let bufferSize: number = 0; +let audioRenderer: audio.AudioRenderer | undefined = undefined; +let audioStreamInfo: audio.AudioStreamInfo = { + samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000, // 采样率。 + channels: audio.AudioChannel.CHANNEL_2, // 通道。 + sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, // 采样格式。 + encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW // 编码格式。 +}; +let audioRendererInfo: audio.AudioRendererInfo = { + // 需使用通话场景相应的参数。 + usage: audio.StreamUsage.STREAM_USAGE_VOICE_COMMUNICATION, // 音频流使用类型:VoIP通话。 + rendererFlags: 0 // 音频渲染器标志:默认为0即可。 +}; +let audioRendererOptions: audio.AudioRendererOptions = { + streamInfo: audioStreamInfo, + rendererInfo: audioRendererInfo +}; +let file: fs.File; +let writeDataCallback: audio.AudioRendererWriteDataCallback; +// [StartExclude all_VoIPDemoForAudioRenderer] +// 全局UI更新回调 +let globalLogUpdate: ((msg: string, isError: boolean) => void) | undefined; +let globalCallbackUpdate: ((msg: string) => void) | undefined; +// [EndExclude all_VoIPDemoForAudioRenderer] +async function initArguments(context: common.UIAbilityContext) { + let path = context.cacheDir; + // 此处仅作示例,实际使用时需要将文件替换为应用要播放的PCM文件。 + let filePath = path + '/StarWars10s-2C-48000-4SW.pcm'; + file = fs.openSync(filePath, fs.OpenMode.READ_ONLY); + writeDataCallback = (buffer: ArrayBuffer) => { + let options: Options = { + offset: bufferSize, + length: buffer.byteLength + }; + + try { + let bufferLength = fs.readSync(file.fd, buffer, options); + bufferSize += buffer.byteLength; + // 如果当前回调传入的数据不足一帧,空白区域需要使用静音数据填充,否则会导致播放出现杂音。 + if (bufferLength < buffer.byteLength) { + let view = new DataView(buffer); + for (let i = bufferLength; i < buffer.byteLength; i++) { + // 空白区域填充静音数据。当使用音频采样格式为SAMPLE_FORMAT_U8时0x7F为静音数据,使用其他采样格式时0为静音数据。 + view.setUint8(i, 0); + } + } + // API version 11不支持返回回调结果,从API version 12开始支持返回回调结果。 + // 如果开发者不希望播放某段buffer,返回audio.AudioDataCallbackResult.INVALID即可。 + return audio.AudioDataCallbackResult.VALID; + } catch (error) { + console.error('Error reading file:', error); + + if (globalLogUpdate) { + globalLogUpdate(`Error reading file: ${error}`, true); + } + // API version 11不支持返回回调结果,从API version 12开始支持返回回调结果。 + return audio.AudioDataCallbackResult.INVALID; + } + }; +} + +// 初始化,创建实例,设置监听事件。 +async function init() { + audio.createAudioRenderer(audioRendererOptions, (err, renderer) => { // 创建AudioRenderer实例。 + if (!err) { + console.info(`${TAG}: creating AudioRenderer success`); + // [StartExclude all_VoIPDemoForAudioRenderer] + const successMsg = `${TAG}: creating AudioRenderer success`; + if (globalLogUpdate) { + globalLogUpdate(successMsg, false); + } + // [EndExclude all_VoIPDemoForAudioRenderer] + audioRenderer = renderer; + if (audioRenderer !== undefined) { + audioRenderer.on('writeData', writeDataCallback); + } + } else { + console.info(`${TAG}: creating AudioRenderer failed, error: ${err.message}`); + // [StartExclude all_VoIPDemoForAudioRenderer] + const errorMsg = `${TAG}: creating AudioRenderer failed, error: ${err.message}`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude all_VoIPDemoForAudioRenderer] + } + }); +} + +// 开始一次音频渲染。 +async function start() { + if (audioRenderer !== undefined) { + let stateGroup = [audio.AudioState.STATE_PREPARED, audio.AudioState.STATE_PAUSED, audio.AudioState.STATE_STOPPED]; + if (stateGroup.indexOf(audioRenderer.state.valueOf()) === -1) { // 当且仅当状态为prepared、paused和stopped之一时才能启动渲染。 + console.error(TAG + 'start failed'); + // [StartExclude all_VoIPDemoForAudioRenderer] + const errorMsg = `${TAG} start failed`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude all_VoIPDemoForAudioRenderer] + return; + } + // 启动渲染。 + audioRenderer.start((err: BusinessError) => { + if (err) { + console.error('Renderer start failed.'); + // [StartExclude all_VoIPDemoForAudioRenderer] + const errorMsg = `Renderer start failed.`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude all_VoIPDemoForAudioRenderer] + } else { + console.info('Renderer start success.'); + // [StartExclude all_VoIPDemoForAudioRenderer] + if (globalLogUpdate) { + globalLogUpdate('Renderer start success.', false); + } + // [EndExclude all_VoIPDemoForAudioRenderer] + } + }); + } +} + +// 暂停渲染。 +async function pause() { + if (audioRenderer !== undefined) { + // 只有渲染器状态为running的时候才能暂停。 + if (audioRenderer.state.valueOf() !== audio.AudioState.STATE_RUNNING) { + console.info('Renderer is not running'); + // [StartExclude all_VoIPDemoForAudioRenderer] + const infoMsg = `Renderer is not running`; + if (globalLogUpdate) { + globalLogUpdate(infoMsg, false); + } + // [EndExclude all_VoIPDemoForAudioRenderer] + return; + } + // 暂停渲染。 + audioRenderer.pause((err: BusinessError) => { + if (err) { + console.error('Renderer pause failed.'); + // [StartExclude all_VoIPDemoForAudioRenderer] + const errorMsg = `Renderer pause failed.`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude all_VoIPDemoForAudioRenderer] + } else { + console.info('Renderer pause success.'); + // [StartExclude all_VoIPDemoForAudioRenderer] + if (globalLogUpdate) { + globalLogUpdate('Renderer pause success.', false); + } + // [EndExclude all_VoIPDemoForAudioRenderer] + } + }); + } +} + +// 停止渲染。 +async function stop() { + if (audioRenderer !== undefined) { + // 只有渲染器状态为running或paused的时候才可以停止。 + if (audioRenderer.state.valueOf() !== audio.AudioState.STATE_RUNNING && + audioRenderer.state.valueOf() !== audio.AudioState.STATE_PAUSED) { + console.info('Renderer is not running or paused.'); + // [StartExclude all_VoIPDemoForAudioRenderer] + const infoMsg = `Renderer is not running or paused.`; + if (globalLogUpdate) { + globalLogUpdate(infoMsg, false); + } + // [EndExclude all_VoIPDemoForAudioRenderer] + return; + } + // 停止渲染。 + audioRenderer.stop((err: BusinessError) => { + if (err) { + console.error('Renderer stop failed.'); + // [StartExclude all_VoIPDemoForAudioRenderer] + const errorMsg = `Renderer stop failed.`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude all_VoIPDemoForAudioRenderer] + } else { + fs.close(file); + console.info('Renderer stop success.'); + // [StartExclude all_VoIPDemoForAudioRenderer] + const successMsg = `Renderer stop success.`; + if (globalLogUpdate) { + globalLogUpdate(successMsg, false); + } + // [EndExclude all_VoIPDemoForAudioRenderer] + } + }); + } +} + +// 销毁实例,释放资源。 +async function release() { + if (audioRenderer !== undefined) { + // 渲染器状态不是released状态,才能release。 + if (audioRenderer.state.valueOf() === audio.AudioState.STATE_RELEASED) { + console.info('Renderer already released'); + // [StartExclude all_VoIPDemoForAudioRenderer] + if (globalLogUpdate) { + globalLogUpdate('Renderer already released', false); + } + // [EndExclude all_VoIPDemoForAudioRenderer] + return; + } + // 释放资源。 + audioRenderer.release((err: BusinessError) => { + if (err) { + console.error('Renderer release failed.'); + // [StartExclude all_VoIPDemoForAudioRenderer] + const errorMsg = `Renderer release failed.`; + if (globalLogUpdate) { + globalLogUpdate(errorMsg, true); + } + // [EndExclude all_VoIPDemoForAudioRenderer] + } else { + console.info('Renderer release success.'); + // [StartExclude all_VoIPDemoForAudioRenderer] + if (globalLogUpdate) { + globalLogUpdate('Renderer release success.', false); + } + // [EndExclude all_VoIPDemoForAudioRenderer] + } + }); + } +} +// [End all_VoIPDemoForAudioRenderer] + +@Entry +@Component +struct Index { + @State currentState: string = '未初始化'; + @State logMessages: string = '暂无日志信息'; + @State callbackMessages: string = '暂无回调信息'; + + aboutToAppear(): void { + // 设置全局回调 + globalLogUpdate = (msg, isError) => this.updateLogInfo(msg, isError); + globalCallbackUpdate = (msg) => this.updateCallbackInfo(msg); + } + + // 更新日志信息 + updateLogInfo(msg: string, isError: boolean): void { + const timestamp = new Date().toLocaleTimeString(); + const prefix = isError ? '[ERROR]' : '[INFO]'; + this.logMessages = `[${timestamp}] ${prefix} ${msg}`; + } + + // 更新回调信息 + updateCallbackInfo(msg: string): void { + const timestamp = new Date().toLocaleTimeString(); + this.callbackMessages = `[${timestamp}] ${msg}`; + } + + build(): void { + Scroll() { + Column() { + // 信息显示区域 + Column() { + Text('实时状态信息') + .fontSize(18) + .fontWeight(600) + .margin({ bottom: 12 }) + + // 当前状态 + Column() { + Text('当前状态') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Text(this.currentState) + .fontSize(14) + .fontColor('#007DFF') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 日志信息 + Column() { + Text('日志信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.logMessages) + .fontSize(12) + .fontColor('#52C41A') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 12 }) + + // 回调信息 + Column() { + Text('数据回调信息') + .fontSize(14) + .fontWeight(600) + .margin({ bottom: 8 }) + Scroll() { + Text(this.callbackMessages) + .fontSize(12) + .fontColor('#FA8C16') + .width('100%') + .padding(10) + .backgroundColor('#F0F0F0') + .borderRadius(8) + } + .width('100%') + } + .width('100%') + .alignItems(HorizontalAlign.Start) + .margin({ bottom: 20 }) + } + .width('100%') + .padding(16) + .backgroundColor(Color.White) + .borderRadius(12) + .margin({ bottom: 16 }) + + // 功能按钮 + Row() { + Column() { + Text('初始化').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 8, bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在初始化...'; + let context = this.getUIContext().getHostContext() as common.UIAbilityContext; + await initArguments(context); + await init(); + this.currentState = '初始化完成'; + }); + + Column() { + Text('开始播放').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在开始播放...'; + await start(); + this.currentState = '播放中'; + }); + } + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + + Row() { + Column() { + Text('暂停播放').fontColor(Color.Black).fontSize(14); + } + .id('audio_effect_manager_card') + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 8, bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在暂停播放...'; + await pause(); + this.currentState = '已暂停'; + }); + + Column() { + Text('停止播放').fontColor(Color.Black).fontSize(14); + } + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在停止播放...'; + await stop(); + this.currentState = '已停止'; + }); + } + .width('100%') + .justifyContent(FlexAlign.SpaceBetween) + + Row() { + Column() { + Text('释放资源').fontColor(Color.Black).fontSize(14); + } + .id('audio_volume_card') + .backgroundColor(Color.White) + .borderRadius(20) + .width('48%') + .height(60) + .justifyContent(FlexAlign.Center) + .margin({ right: 8, bottom: 12 }) + .onClick(async (): Promise => { + this.currentState = '正在释放资源...'; + await release(); + this.currentState = '已释放'; + }); + } + .width('100%') + .justifyContent(FlexAlign.Start) + } + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5') + .padding(16); + } + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/module.json5 b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..21f6476d44824f09ee96660082c9bd05f234c717 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/module.json5 @@ -0,0 +1,86 @@ +/* +* 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": [ + "default" + ], + "deliveryWithInstall": true, + "installationFree": false, + "pages": "$profile:main_pages", + "abilities": [ + { + "name": "EntryAbility", + "srcEntry": "./ets/entryability/EntryAbility.ets", + "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": [ + "ohos.want.action.home" + ] + } + ] + } + ], + "extensionAbilities": [ + { + "name": "EntryBackupAbility", + "srcEntry": "./ets/entrybackupability/EntryBackupAbility.ets", + "type": "backup", + "exported": false, + "metadata": [ + { + "name": "ohos.extension.backup", + "resource": "$profile:backup_config" + } + ], + } + ], + "requestPermissions": [ + { + "name": "ohos.permission.MICROPHONE", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "always" + } + }, + { + "name": "ohos.permission.ACCESS_NOTIFICATION_POLICY", + "reason": "$string:EntryAbility_desc", + "usedScene": { + "abilities": [ + "EntryAbility" + ], + "when": "always" + } + }, + ] + } +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/element/color.json b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..3c712962da3c2751c2b9ddb53559afcbd2b54a02 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/element/color.json @@ -0,0 +1,8 @@ +{ + "color": [ + { + "name": "start_window_background", + "value": "#FFFFFF" + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/element/float.json b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/element/float.json new file mode 100644 index 0000000000000000000000000000000000000000..801bc27a39ab78e230cb049d46c0b4f7acb5e01d --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/element/float.json @@ -0,0 +1,16 @@ +{ + "float": [ + { + "name": "page_text_font_size", + "value": "50fp" + }, + { + "name": "index_text_font_size", + "value": "50fp" + }, + { + "name": "index_button_height_size", + "value": "80vp" + } + ] +} diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/element/string.json b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/element/string.json new file mode 100644 index 0000000000000000000000000000000000000000..f94595515a99e0c828807e243494f57f09251930 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/element/string.json @@ -0,0 +1,16 @@ +{ + "string": [ + { + "name": "module_desc", + "value": "module description" + }, + { + "name": "EntryAbility_desc", + "value": "description" + }, + { + "name": "EntryAbility_label", + "value": "label" + } + ] +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/media/background.png b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/media/background.png new file mode 100644 index 0000000000000000000000000000000000000000..923f2b3f27e915d6871871deea0420eb45ce102f Binary files /dev/null and b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/media/background.png differ diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/media/foreground.png b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/media/foreground.png new file mode 100644 index 0000000000000000000000000000000000000000..97014d3e10e5ff511409c378cd4255713aecd85f Binary files /dev/null and b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/media/foreground.png differ diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/media/layered_image.json b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/media/layered_image.json new file mode 100644 index 0000000000000000000000000000000000000000..fb49920440fb4d246c82f9ada275e26123a2136a --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/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/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/media/startIcon.png b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/media/startIcon.png new file mode 100644 index 0000000000000000000000000000000000000000..205ad8b5a8a42e8762fbe4899b8e5e31ce822b8b Binary files /dev/null and b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/media/startIcon.png differ diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/profile/backup_config.json b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/profile/backup_config.json new file mode 100644 index 0000000000000000000000000000000000000000..78f40ae7c494d71e2482278f359ec790ca73471a --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/profile/backup_config.json @@ -0,0 +1,3 @@ +{ + "allowToBackupRestore": true +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/profile/main_pages.json b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/profile/main_pages.json new file mode 100644 index 0000000000000000000000000000000000000000..0b98b9d98109e787561b19cba330104fa4278c5f --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/base/profile/main_pages.json @@ -0,0 +1,7 @@ +{ + "src": [ + "pages/Index", + "pages/VoIpDemoForAudioCapturer", + "pages/VoIpDemoForAudioRenderer" + ] +} diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/dark/element/color.json b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/main/resources/dark/element/color.json new file mode 100644 index 0000000000000000000000000000000000000000..79b11c2747aec33e710fd3a7b2b3c94dd9965499 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/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/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..621da0d393a21467d95052cf350dd15f438996e8 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/ets/test/Ability.test.ets @@ -0,0 +1,49 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function abilityTest() { + describe('ActsAbilityTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }) + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }) + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }) + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }) + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + hilog.info(0x0000, 'testTag', '%{public}s', 'it begin'); + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }) + }) +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/ets/test/List.test.ets b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/ets/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..78c6ce3e9644bdcdbb603170397badfcdd28e8af --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/ets/test/List.test.ets @@ -0,0 +1,21 @@ +/* +* 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 VoIPAudioCapturerDemoTest from './VoIpDemoForAudioCapturer.test'; +import VoIPAudioRendererDemoTest from './VoIpDemoForAudioRenderer.test'; + +export default function testsuite() { + VoIPAudioCapturerDemoTest(); + VoIPAudioRendererDemoTest(); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/ets/test/VoIpDemoForAudioCapturer.test.ets b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/ets/test/VoIpDemoForAudioCapturer.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..b5a23b3a44b76cb85b5037000e4e71e8cf42da3d --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/ets/test/VoIpDemoForAudioCapturer.test.ets @@ -0,0 +1,165 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, Level } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const TAG = '[VoIPDemoForAudioCapturer]'; +const DOMAIN = 0xF81A; +const BUNDLE = 'VoIPDemoForAudioCapturer_'; +let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const driver = Driver.create(); + +async function runTest(testCaseName: string, buttonText: string, delayTime: number, description: string, + done: Function) { + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, begin`); + + let btn = await driver.findComponent(ON.text(buttonText)); + if (btn !== undefined) { + await btn.click(); + await driver.delayMs(delayTime); + } else { + hilog.warn(DOMAIN, TAG, BUNDLE + `Button "${buttonText}" not found.`); + await driver.delayMs(delayTime); + } + + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, end`); + done(); +} + +export default function VoIPDemoForAudioCapturerTest() { + describe('VoIPDemoForAudioCapturerTest', () => { + beforeAll(() => { + abilityDelegator.startAbility({ + bundleName: 'com.example.myapplication', + abilityName: 'EntryAbility' + }); + }); + + beforeEach(() => {}) + afterEach(() => {}) + afterAll(() => {}) + + /** + * @tc.number VoIPDemoForAudioCapturer_EnterPage_0001 + * @tc.name VoIPDemoForAudioCapturer_EnterPage_0001 + * @tc.desc Navigate to VoIP audio capturer demo page from main page + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioCapturer_EnterPage_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioCapturer_EnterPage_0001', + '使用AudioCapturer录制本端的通话声音页面', + 5000, + 'Navigate to VoIP audio capturer demo page', + done + ); + }); + + /** + * @tc.number VoIPDemoForAudioCapturer_Initialize_0001 + * @tc.name VoIPDemoForAudioCapturer_Initialize_0001 + * @tc.desc Click Initialize button to create AudioCapturer instance + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioCapturer_Initialize_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioCapturer_Initialize_0001', + '初始化', + 6000, + 'Initialize AudioCapturer and request microphone permission', + done + ); + }); + + /** + * @tc.number VoIPDemoForAudioCapturer_RequestPermission_0001 + * @tc.name VoIPDemoForAudioCapturer_RequestPermission_0001 + * @tc.desc Click Request Permission button to trigger microphone permission dialog + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioCapturer_RequestPermission_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioCapturer_RequestPermission_0001', + '允许', + 3000, + 'Click to request microphone permission before initialization', + done + ); + }); + + /** + * @tc.number VoIPDemoForAudioCapturer_StartRecording_0001 + * @tc.name VoIPDemoForAudioCapturer_StartRecording_0001 + * @tc.desc Click Start Recording button to begin audio capture + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioCapturer_StartRecording_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioCapturer_StartRecording_0001', + '开始录制', + 3000, + 'Start audio recording via AudioCapturer', + done + ); + }); + + /** + * @tc.number VoIPDemoForAudioCapturer_StopRecording_0001 + * @tc.name VoIPDemoForAudioCapturer_StopRecording_0001 + * @tc.desc Click Stop Recording button to halt capture and close file + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioCapturer_StopRecording_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioCapturer_StopRecording_0001', + '停止录制', + 4000, + 'Stop audio recording and finalize file write', + done + ); + }); + + /** + * @tc.number VoIPDemoForAudioCapturer_ReleaseResources_0001 + * @tc.name VoIPDemoForAudioCapturer_ReleaseResources_0001 + * @tc.desc Click Release Resources button to destroy AudioCapturer + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioCapturer_ReleaseResources_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioCapturer_ReleaseResources_0001', + '释放资源', + 3000, + 'Release AudioCapturer and free system resources', + done + ); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/ets/test/VoIpDemoForAudioRenderer.test.ets b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/ets/test/VoIpDemoForAudioRenderer.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..acbc36faa3dfbd22376d99378e9bea13725ee487 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/ets/test/VoIpDemoForAudioRenderer.test.ets @@ -0,0 +1,165 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, Level } from '@ohos/hypium'; +import { Driver, ON } from '@ohos.UiTest'; +import abilityDelegatorRegistry from '@ohos.app.ability.abilityDelegatorRegistry'; + +const TAG = '[VoIPDemoForAudioRenderer]'; +const DOMAIN = 0xF81A; +const BUNDLE = 'VoIPDemoForAudioRenderer_'; +let abilityDelegator = abilityDelegatorRegistry.getAbilityDelegator(); +const driver = Driver.create(); + +async function runTest(testCaseName: string, buttonText: string, delayTime: number, description: string, + done: Function) { + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, begin`); + + let btn = await driver.findComponent(ON.text(buttonText)); + if (btn !== undefined) { + await btn.click(); + await driver.delayMs(delayTime); + } else { + hilog.warn(DOMAIN, TAG, BUNDLE + `Button "${buttonText}" not found.`); + await driver.delayMs(delayTime); + } + + hilog.info(DOMAIN, TAG, BUNDLE + `${description}, end`); + done(); +} + +export default function VoIPDemoForAudioRendererTest() { + describe('VoIPDemoForAudioRendererTest', () => { + beforeAll(() => { + abilityDelegator.startAbility({ + bundleName: 'com.example.myapplication', + abilityName: 'EntryAbility' + }); + }); + + beforeEach(() => {}) + afterEach(() => {}) + afterAll(() => {}) + + /** + * @tc.number VoIPDemoForAudioRenderer_EnterPage_0001 + * @tc.name VoIPDemoForAudioRenderer_EnterPage_0001 + * @tc.desc Navigate to the VoIP audio renderer demo page from the main entry + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioRenderer_EnterPage_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioRenderer_EnterPage_0001', + '使用AudioRenderer播放对端的通话声音页面', + 5000, + 'Navigate to VoIP audio renderer demo page', + done + ); + }); + + /** + * @tc.number VoIPDemoForAudioRenderer_Initialize_0001 + * @tc.name VoIPDemoForAudioRenderer_Initialize_0001 + * @tc.desc Click the Initialize button to create an AudioRenderer instance and prepare PCM file + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioRenderer_Initialize_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioRenderer_Initialize_0001', + '初始化', + 6000, + 'Initialize AudioRenderer and prepare PCM file', + done + ); + }); + + /** + * @tc.number VoIPDemoForAudioRenderer_StartPlayback_0001 + * @tc.name VoIPDemoForAudioRenderer_StartPlayback_0001 + * @tc.desc Click the Start Playback button to begin audio rendering + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioRenderer_StartPlayback_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioRenderer_StartPlayback_0001', + '开始播放', + 3000, + 'Start audio playback via AudioRenderer', + done + ); + }); + + /** + * @tc.number VoIPDemoForAudioRenderer_PausePlayback_0001 + * @tc.name VoIPDemoForAudioRenderer_PausePlayback_0001 + * @tc.desc Click the Pause Playback button to pause ongoing audio rendering + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioRenderer_PausePlayback_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioRenderer_PausePlayback_0001', + '暂停播放', + 3000, + 'Pause ongoing audio playback', + done + ); + }); + + /** + * @tc.number VoIPDemoForAudioRenderer_StopPlayback_0001 + * @tc.name VoIPDemoForAudioRenderer_StopPlayback_0001 + * @tc.desc Click the Stop Playback button to stop rendering and close PCM file + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioRenderer_StopPlayback_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioRenderer_StopPlayback_0001', + '停止播放', + 4000, + 'Stop audio playback and close PCM file', + done + ); + }); + + /** + * @tc.number VoIPDemoForAudioRenderer_ReleaseResources_0001 + * @tc.name VoIPDemoForAudioRenderer_ReleaseResources_0001 + * @tc.desc Click the Release Resources button to destroy AudioRenderer and free system resources + * @tc.size MediumTest + * @tc.type Function + * @tc.level Level 1 + */ + it('VoIPDemoForAudioRenderer_ReleaseResources_0001', Level.LEVEL1, async (done: Function) => { + await runTest( + 'VoIPDemoForAudioRenderer_ReleaseResources_0001', + '释放资源', + 3000, + 'Release AudioRenderer and free system resources', + done + ); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/module.json5 b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/module.json5 new file mode 100644 index 0000000000000000000000000000000000000000..0a099ee4bd05a001f943915cd4eb2e01aabeaca0 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/ohosTest/module.json5 @@ -0,0 +1,25 @@ +/* +* 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_test", + "type": "feature", + "deviceTypes": [ + "phone" + ], + "deliveryWithInstall": true, + "installationFree": false + } +} diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/test/List.test.ets b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/test/List.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..c2359615fdfb661f743a2cac5886892209c7aee6 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/test/List.test.ets @@ -0,0 +1,19 @@ +/* +* 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 localUnitTest from './LocalUnit.test'; + +export default function testsuite() { + localUnitTest(); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/entry/src/test/LocalUnit.test.ets b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/test/LocalUnit.test.ets new file mode 100644 index 0000000000000000000000000000000000000000..45ca114e698503447e7ccc80364c54cf7fa50f46 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/entry/src/test/LocalUnit.test.ets @@ -0,0 +1,47 @@ +/* +* 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 { describe, beforeAll, beforeEach, afterEach, afterAll, it, expect } from '@ohos/hypium'; + +export default function localUnitTest() { + describe('localUnitTest', () => { + // Defines a test suite. Two parameters are supported: test suite name and test suite function. + beforeAll(() => { + // Presets an action, which is performed only once before all test cases of the test suite start. + // This API supports only one parameter: preset action function. + }); + beforeEach(() => { + // Presets an action, which is performed before each unit test case starts. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: preset action function. + }); + afterEach(() => { + // Presets a clear action, which is performed after each unit test case ends. + // The number of execution times is the same as the number of test cases defined by **it**. + // This API supports only one parameter: clear action function. + }); + afterAll(() => { + // Presets a clear action, which is performed after all test cases of the test suite end. + // This API supports only one parameter: clear action function. + }); + it('assertContain', 0, () => { + // Defines a test case. This API supports three parameters: test case name, filter parameter, and test case function. + let a = 'abc'; + let b = 'b'; + // Defines a variety of assertion methods, which are used to declare expected boolean conditions. + expect(a).assertContain(b); + expect(a).assertEqual(a); + }); + }); +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/hvigor/hvigor-config.json5 b/MediaKit/AudioKit/VoipCallSampleJS/hvigor/hvigor-config.json5 new file mode 100644 index 0000000000000000000000000000000000000000..7d7d5f8aa0d0c54d1c8daf78625b9b3bbf52aee8 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/hvigor/hvigor-config.json5 @@ -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. +*/ +{ + "modelVersion": "6.0.0", + "dependencies": { + }, + "execution": { + // "analyze": "normal", /* Define the build analyze mode. Value: [ "normal" | "advanced" | "ultrafine" | 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 */ + // "optimizationStrategy": "memory" /* Define the optimization strategy. Value: [ "memory" | "performance" ]. Default: "memory" */ + }, + "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/MediaKit/AudioKit/VoipCallSampleJS/hvigorfile.ts b/MediaKit/AudioKit/VoipCallSampleJS/hvigorfile.ts new file mode 100644 index 0000000000000000000000000000000000000000..7c5b9939f0c562551a58ebd26abae33caf4f5e28 --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/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. */ +} \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/oh-package.json5 b/MediaKit/AudioKit/VoipCallSampleJS/oh-package.json5 new file mode 100644 index 0000000000000000000000000000000000000000..d4c67d9bed2040005240f4f66a6601227e30809b --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/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": "6.0.0", + "description": "Please describe the basic information.", + "dependencies": { + }, + "devDependencies": { + "@ohos/hypium": "1.0.24", + "@ohos/hamock": "1.0.0" + } +} diff --git a/MediaKit/AudioKit/VoipCallSampleJS/ohosTest.md b/MediaKit/AudioKit/VoipCallSampleJS/ohosTest.md new file mode 100644 index 0000000000000000000000000000000000000000..176b059ada441e3c4ef25c127de138fce95bbfcd --- /dev/null +++ b/MediaKit/AudioKit/VoipCallSampleJS/ohosTest.md @@ -0,0 +1,19 @@ +# 音频通话测试用例归档 + +## 用例表 + +| 测试功能 | 预置条件 | 输入 | 预期输出 | 是否自动 | +|----------------------------------|--------------|------------------------------------|----------------------------------------|---------| +| 拉起应用 | 设备正常运行 | - | 成功拉起应用,进入首页 | 是 | +| 进入使用音频录制本端的通话声音页面 | 位于主页 | 点击"使用音频录制本端的通话声音页面" | 进入 VoIP AudioCapturer 演示页面 | 是 | +| 初始化 AudioCapturer | 录制页面 | 点击"初始化" | 创建 AudioCapturer 实例并请求麦克风权限 | 是 | +| 请求麦克风权限 | 录制页面(未授权) | 点击"允许" | 触发并授予麦克风权限 | 是 | +| 开始录制 | AudioCapturer 已初始化 | 点击"开始录制" | 启动音频录制 | 是 | +| 停止录制 | 录制进行中 | 点击"停止录制" | 停止录制并完成文件写入 | 是 | +| 释放 AudioCapturer 资源 | 录制已停止 | 点击"释放资源" | 销毁 AudioCapturer 并释放系统资源 | 是 | +| 进入使用音频播放对端的通话声音页面 | 位于主页 | 点击"使用音频播放对端的通话声音页面" | 进入 VoIP AudioRenderer 演示页面 | 是 | +| 初始化 AudioRenderer | 播放页面 | 点击"初始化" | 创建 AudioRenderer 实例并准备 PCM 文件 | 是 | +| 开始播放 | AudioRenderer 已初始化 | 点击"开始播放" | 启动音频播放 | 是 | +| 暂停播放 | 播放进行中 | 点击"暂停播放" | 暂停当前音频播放 | 是 | +| 停止播放 | 播放进行中或已暂停 | 点击"停止播放" | 停止播放并关闭 PCM 文件 | 是 | +| 释放 AudioRenderer 资源 | 播放已停止 | 点击"释放资源" | 销毁 AudioRenderer 并释放系统资源 | 是 | \ No newline at end of file diff --git a/MediaKit/AudioKit/VoipCallSampleJS/screenShots/VOIP_Capture.png b/MediaKit/AudioKit/VoipCallSampleJS/screenShots/VOIP_Capture.png new file mode 100644 index 0000000000000000000000000000000000000000..b7be8879fc0e9a83f788ec70d4b6e789374cf7df Binary files /dev/null and b/MediaKit/AudioKit/VoipCallSampleJS/screenShots/VOIP_Capture.png differ diff --git a/MediaKit/AudioKit/VoipCallSampleJS/screenShots/VOIP_Render.png b/MediaKit/AudioKit/VoipCallSampleJS/screenShots/VOIP_Render.png new file mode 100644 index 0000000000000000000000000000000000000000..d77a4ea8f930c308d4f75de77639bea11661d972 Binary files /dev/null and b/MediaKit/AudioKit/VoipCallSampleJS/screenShots/VOIP_Render.png differ diff --git a/MediaKit/AudioKit/VoipCallSampleJS/screenShots/VOIP_index.png b/MediaKit/AudioKit/VoipCallSampleJS/screenShots/VOIP_index.png new file mode 100644 index 0000000000000000000000000000000000000000..f36cc1ef45effb2d7aa7b2b7f62ac562d8511415 Binary files /dev/null and b/MediaKit/AudioKit/VoipCallSampleJS/screenShots/VOIP_index.png differ