diff --git a/code/BasicFeature/Media/Audio/AppScope/resources/base/element/string.json b/code/BasicFeature/Media/Audio/AppScope/resources/base/element/string.json index 151449bec52e7a8293a28a2606fbb9e8859abed1..5c21505c026eb4c1e82e461eb9357f48be74a77b 100644 --- a/code/BasicFeature/Media/Audio/AppScope/resources/base/element/string.json +++ b/code/BasicFeature/Media/Audio/AppScope/resources/base/element/string.json @@ -259,6 +259,70 @@ { "name": "5P1_MUSIC", "value": "5.1音乐示例" + }, + { + "name": "KARAOKE", + "value": "k歌" + }, + { + "name": "KARAOKE_DEMO_TITLE", + "value": "K歌功能演示" + }, + { + "name": "KARAOKE_STATUS_PREFIX", + "value": "当前状态:" + }, + { + "name": "KARAOKE_NOT_INITIALIZED", + "value": "未初始化" + }, + { + "name": "KARAOKE_INITIALIZED", + "value": "已初始化" + }, + { + "name": "KARAOKE_INITIALIZATION_FAILED", + "value": "初始化失败" + }, + { + "name": "KARAOKE_UNSUPPORTED_HARDWARE", + "value": "平台不支持硬返" + }, + { + "name": "KARAOKE_DEVICE_UNSUPPORTED", + "value": "当前设备不支持" + }, + { + "name": "KARAOKE_SCENE_UNSUPPORTED", + "value": "当前场景不支持" + }, + { + "name": "KARAOKE_IDLE", + "value": "空闲中" + }, + { + "name": "KARAOKE_RUNNING", + "value": "返听中" + }, + { + "name": "KARAOKE_NO_INSTANCE", + "value": "未创建返听实例" + }, + { + "name": "KARAOKE_INIT_BUTTON", + "value": "初始化" + }, + { + "name": "KARAOKE_ENABLE_BUTTON", + "value": "开启返听" + }, + { + "name": "KARAOKE_DISABLE_BUTTON", + "value": "停止返听" + }, + { + "name": "KARAOKE_VOLUME_LABEL", + "value": "设置返听音量:" } ] } diff --git a/code/BasicFeature/Media/Audio/README.md b/code/BasicFeature/Media/Audio/README.md index 4f19a11c5e3c2d8730d43924e6ec0cf086dac19b..463e586a13974be4b0cdcc012e4957eb1d5600dd 100644 --- a/code/BasicFeature/Media/Audio/README.md +++ b/code/BasicFeature/Media/Audio/README.md @@ -18,6 +18,10 @@ |-----------------------------------------|----------------------------------------------------|---------------------------------------------------------------------|---------------------------------------------------------------------| | ![LiveCapturer](screenshots/device/live_capturerpng.jpg)| ![VolumePanel](screenshots/device/VolumePanel.png) | ![VolumePanel](screenshots/device/VolumePanel_ChangeVolumLevel.png) | ![SpatialAudio](screenshots/device/SpatialAudio.jpg) | +| 音频k歌页面 | +|-----------------------------------------| +| ![Karaoke](screenshots/device/karaoke.jpeg)| + 使用说明 注意:6,7,8是连续的串行操作,不是并行的。空间音频需要在具体路径上添加pcm文件。 @@ -77,7 +81,11 @@ hdc file send 5p1.pcm data/app/el2/100/base/com.samples.audio/haps/entry/files/ 49. 在空间音频页面,当设备支持空间音频且空间音频开关已开启时,点击“5.0音乐示例”播放器,开始播放AudioVivid音源 50. 在空间音频页面,当设备支持空间音频且空间音频开关被打开时,出现弹窗提示“空间音频已开启” 51. 在空间音频页面,当设备支持空间音频且空间音频开关被关闭时,出现弹窗提示“空间音频已关闭” - +52. 在主界面点击“k歌”按钮,进入k歌页面 +53. 在k歌页面,点击初始化按钮,创建AudioLoopback实例,在不支持平台会显示“平台不支持硬返”,支持平台会显示“已初始化”。 +54. 在k歌页面,点击开启返听按钮,开启耳返,在设备不支持的时候会显示“当前设备不支持”,场景不支持的时候会显示“当前场景不支持”,开启耳返成功会显示“返听中”。 +55. 在k歌页面,点击关闭耳返按钮,关闭耳返。 +56. 在k歌页面,滑动音量条,调整耳返音量。 ### 工程目录 @@ -94,6 +102,7 @@ entry/src/main/ets/ |---|---LiveCapturer.ets //音频录制-直播录制 |---|---VolumePanel.ets //音量组件页面 |---|---SpatialAudio.ets //空间音频页面 +|---|---Karaoke.ets //k歌页面 library/ |---Logger.ts //日志打印封装 ``` @@ -164,7 +173,14 @@ library/ * 调用isSpatializationEnabledForCurrentDevice()接口查询当前发声设备空间音频开关状态 * 调用on("spatializationEnabledChangeForCurrentDevice")接口订阅当前发声设备空间音频空间音频开关状态变化事件 * 调用off("spatializationEnabledChangeForCurrentDevice")接口取消订阅当前发声设备空间音频空间音频开关状态变化事件 - +* k歌功能都封装在Karaoke,源码参考:[Karaoke.ets](entry/src/main/ets/pages/Karaoke.ets) + * 调用audio.getAudioManager().getStreamManager().isAudioLoopbackSupported(mode)查询当前平台是否支持返听能力。 + * 调用audio.createAudioLoopback(mode)创建AudioLoopback实例。 + * 调用getStatus方法,查询当前返听状态。 + * 调用setVolume方法,设置音频返听音量。 + * 调用enable方法,启用或禁用音频返听功能。 + * 调用on("statusChange")接口订阅当前返听状态变化事件 + * 调用off("statusChange")接口取消订阅返听状态变化事件 ### 相关权限 音频录制涉及的权限包括: diff --git a/code/BasicFeature/Media/Audio/entry/src/main/ets/pages/Index.ets b/code/BasicFeature/Media/Audio/entry/src/main/ets/pages/Index.ets index 37685a9fb11c32356fb957b38610fd6dcc31e38c..0839320ec8912e9489754d2c5a6d8d3b2720295c 100644 --- a/code/BasicFeature/Media/Audio/entry/src/main/ets/pages/Index.ets +++ b/code/BasicFeature/Media/Audio/entry/src/main/ets/pages/Index.ets @@ -18,100 +18,124 @@ import router from '@ohos.router'; @Component struct Index { build() { - Column() { - Row() { - Column() { - Image($r('app.media.png01_findxhdpi')).width(72).height(72).margin({ top: 36 }); - Text($r('app.string.SelectOutputDevice')).fontColor(Color.Black).fontSize(16).margin({ top: 12 }); - } - .id('select_output_device_card') - .backgroundColor(Color.White) - .margin({ right: 12 }) - .width(174) - .height(188) - .borderRadius(30) - .onClick(async () => { - await router.pushUrl({ url: 'pages/PreferOutputDevice' }); - }); + Scroll() { + Column() { + Row() { + Column() { + Image($r('app.media.png01_findxhdpi')).width(72).height(72).margin({ top: 24 }); + Text($r('app.string.SelectOutputDevice')).fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + } + .id('select_output_device_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('25%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + await router.pushUrl({ url: 'pages/PreferOutputDevice' }); + }); - Column() { - Image($r('app.media.png01_audioxhdpi')).width(72).height(72).margin({ top: 36 }); - Text($r('app.string.AudioFocus')).fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + Column() { + Image($r('app.media.png01_audioxhdpi')).width(72).height(72).margin({ top: 24 }); + Text($r('app.string.AudioFocus')).fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + } + .id('audio_focus_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('25%') + .margin({ bottom: 12 }) + .onClick(async () => { + await router.pushUrl({ url: 'pages/Focus' }); + }); } - .id('audio_focus_card') - .backgroundColor(Color.White) - .borderRadius(30) - .width(174) - .height(188) - .onClick(async () => { - await router.pushUrl({ url: 'pages/Focus' }); - }); - } - .margin({ top: 32 }) - .justifyContent(FlexAlign.SpaceBetween) + .justifyContent(FlexAlign.SpaceBetween) - Row() { - Column() { - Image($r('app.media.png01_Soundeffectsxhdpi')).width(72).height(72).margin({ top: 36 }); - Text($r('app.string.AudioEffectManager')).fontColor(Color.Black).fontSize(16).margin({ top: 12 }); - } - .id('audio_effect_manager_card') - .backgroundColor(Color.White) - .borderRadius(30) - .margin({ right: 12 }) - .width(174) - .height(188) - .onClick(async () => { - await router.pushUrl({ url: 'pages/PresetEffect' }); - }) + Row() { + Column() { + Image($r('app.media.png01_Soundeffectsxhdpi')).width(72).height(72).margin({ top: 24 }); + Text($r('app.string.AudioEffectManager')).fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + } + .id('audio_effect_manager_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('25%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + await router.pushUrl({ url: 'pages/PresetEffect' }); + }); - Column() { - Image($r('app.media.png01_Recordingxxxhdpi')).width(72).height(72).margin({ top: 36 }); - Text($r('app.string.AUDIO_CAPTURER')).fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + Column() { + Image($r('app.media.png01_Recordingxxxhdpi')).width(72).height(72).margin({ top: 24 }); + Text($r('app.string.AUDIO_CAPTURER')).fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + } + .id('audio_capturer_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('25%') + .margin({ bottom: 12 }) + .onClick(async () => { + await router.pushUrl({ url: 'pages/NormalCapturer' }); + }); } - .id('audio_capturer_card') - .backgroundColor(Color.White) - .borderRadius(30) - .width(174) - .height(188) - .onClick(async () => { - await router.pushUrl({ url: 'pages/NormalCapturer' }); - }); - } - .margin({ top: 12 }) - .justifyContent(FlexAlign.SpaceBetween) + .justifyContent(FlexAlign.SpaceBetween) - Row() { - Column() { - Image($r('app.media.ic_Sound_select')).width(72).height(72).margin({ top: 36 }); - Text($r('app.string.AVVOLUME_PANEL')).fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + Row() { + Column() { + Image($r('app.media.ic_Sound_select')).width(72).height(72).margin({ top: 24 }); + Text($r('app.string.AVVOLUME_PANEL')).fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + } + .id('audio_volume_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('25%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + await router.pushUrl({ url: 'pages/VolumePanel' }); + }); + + Column() { + Image($r('app.media.ic_index_spatial')).width(72).height(72).margin({ top: 24 }); + Text($r('app.string.SPATIAL_AUDIO')).fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + } + .id('spatial_audio_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('25%') + .margin({ bottom: 12 }) + .onClick(async () => { + await router.pushUrl({ url: 'pages/SpatialAudio' }); + }); } - .id('audio_volume_card') - .backgroundColor(Color.White) - .borderRadius(30) - .margin({ right: 12 }) - .width(174) - .height(188) - .onClick(async () => { - await router.pushUrl({ url: 'pages/VolumePanel' }); - }) + .justifyContent(FlexAlign.SpaceBetween) + + Row() { + Column() { + Image($r('app.media.png01_Karaoke')).width(72).height(72).margin({ top: 24 }); + Text($r('app.string.KARAOKE')).fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + } + .id('karaoke_card') + .backgroundColor(Color.White) + .borderRadius(30) + .width('45%') + .height('25%') + .margin({ right: 12, bottom: 12 }) + .onClick(async () => { + await router.pushUrl({ url: 'pages/Karaoke' }); + }); - Column() { - Image($r('app.media.ic_index_spatial')).width(72).height(72).margin({ top: 36 }); - Text($r('app.string.SPATIAL_AUDIO')).fontColor(Color.Black).fontSize(16).margin({ top: 12 }); + Blank().width('45%').height('25%'); } - .id('spatial_audio_card') - .backgroundColor(Color.White) - .borderRadius(30) - .width(174) - .height(188) - .onClick(async () => { - await router.pushUrl({ url: 'pages/SpatialAudio'}); - }); + .justifyContent(FlexAlign.SpaceBetween) + } + .padding(12) } - .margin({ top: 12 }) - .justifyContent(FlexAlign.SpaceBetween); - } - .height('100%').width('100%').backgroundColor('#F1F3F5'); + .height('100%') + .width('100%') + .backgroundColor('#F1F3F5'); } } \ No newline at end of file diff --git a/code/BasicFeature/Media/Audio/entry/src/main/ets/pages/Karaoke.ets b/code/BasicFeature/Media/Audio/entry/src/main/ets/pages/Karaoke.ets new file mode 100644 index 0000000000000000000000000000000000000000..ccdd2cc35e54b647b0c318265cd1e825f16afa79 --- /dev/null +++ b/code/BasicFeature/Media/Audio/entry/src/main/ets/pages/Karaoke.ets @@ -0,0 +1,190 @@ +/* +* 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 { audio } from '@kit.AudioKit'; +import { BusinessError } from '@kit.BasicServicesKit'; +import { common } from '@kit.AbilityKit'; +import promptAction from '@ohos.promptAction'; +import Logger from '../../../ohosTest/ets/utils/Logger'; +const TAG = 'KaraokePage'; + +@Entry +@Component +struct KaraokePage { + @State audioLoopback: audio.AudioLoopback | undefined = undefined; + @State statusText: Resource = $r('app.string.KARAOKE_NOT_INITIALIZED'); + @State volume: number = 50; + + // Initial loopback mode: hardware + @State mode: audio.AudioLoopbackMode = audio.AudioLoopbackMode.HARDWARE; + + // Audio loopback status change callback + private statusChangeCallback = (status: audio.AudioLoopbackStatus) => { + if (status == audio.AudioLoopbackStatus.UNAVAILABLE_DEVICE) { + this.statusText = $r('app.string.KARAOKE_DEVICE_UNSUPPORTED'); + Logger.info('Audio loopback status is: UNAVAILABLE_DEVICE'); + } else if (status == audio.AudioLoopbackStatus.UNAVAILABLE_SCENE) { + this.statusText = $r('app.string.KARAOKE_SCENE_UNSUPPORTED'); + Logger.info('Audio loopback status is: UNAVAILABLE_SCENE'); + } else if (status == audio.AudioLoopbackStatus.AVAILABLE_IDLE) { + this.statusText = $r('app.string.KARAOKE_IDLE'); + Logger.info('Audio loopback status is: AVAILABLE_IDLE'); + } else if (status == audio.AudioLoopbackStatus.AVAILABLE_RUNNING) { + this.statusText = $r('app.string.KARAOKE_RUNNING'); + Logger.info('Audio loopback status is: AVAILABLE_RUNNING'); + } + this.showStatusText(); + }; + + private showStatusText() { + if (this.statusText) { + promptAction.showToast({ + message: this.statusText, + duration: 2000 + }); + } + } + + // Initialize the audio loopback + async initLoopback() { + let isSupported = audio.getAudioManager().getStreamManager().isAudioLoopbackSupported(this.mode); + if (isSupported) { + audio.createAudioLoopback(this.mode).then((loopback) => { + Logger.info('Invoke createAudioLoopback succeeded.'); + this.audioLoopback = loopback; + this.statusText = $r('app.string.KARAOKE_INITIALIZED'); + this.showStatusText(); + }).catch((err: BusinessError) => { + Logger.error(`Invoke createAudioLoopback failed, code is ${err.code}, message is ${err.message}.`); + this.statusText = $r('app.string.KARAOKE_INITIALIZATION_FAILED'); + this.showStatusText(); + }); + } else { + this.statusText = $r('app.string.KARAOKE_UNSUPPORTED_HARDWARE'); + this.showStatusText(); + Logger.error('Audio loopback is unsupported.'); + } + } + + // Enable audio loopback + async enableLoopback() { + if (this.audioLoopback !== undefined) { + try { + let status = await this.audioLoopback.getStatus(); + if (status == audio.AudioLoopbackStatus.AVAILABLE_IDLE) { + this.audioLoopback.on('statusChange', this.statusChangeCallback); + let success = await this.audioLoopback.enable(true); + if (success) { + Logger.info('Invoke enable succeeded'); + } else { + status = await this.audioLoopback.getStatus(); + this.statusChangeCallback(status); + } + } else { + this.statusChangeCallback(status); + } + } catch (err) { + Logger.error(`Invoke enable failed, code is ${err.code}, message is ${err.message}.`); + } + } else { + this.statusText = $r('app.string.KARAOKE_NO_INSTANCE'); + Logger.error('Audio loopback not created.'); + } + } + + // Disable audio loopback + async disableLoopback() { + if (this.audioLoopback !== undefined) { + try { + let status = await this.audioLoopback.getStatus(); + if (status == audio.AudioLoopbackStatus.AVAILABLE_RUNNING) { + let success = await this.audioLoopback.enable(false); + this.statusText = $r('app.string.KARAOKE_IDLE'); + if (success) { + Logger.info('Invoke disable succeeded'); + this.audioLoopback.off('statusChange', this.statusChangeCallback); + } else { + status = await this.audioLoopback.getStatus(); + this.statusChangeCallback(status); + } + } else { + this.statusChangeCallback(status); + } + } catch (err) { + Logger.error(`Invoke disable failed, code is ${err.code}, message is ${err.message}.`); + } + } else { + this.statusText = $r('app.string.KARAOKE_NO_INSTANCE'); + Logger.error('Audio loopback not created.'); + } + } + + // Set audio loopback volume + async setVolume(newVolume: number) { + this.volume = newVolume; + if (this.audioLoopback !== undefined) { + try { + await this.audioLoopback.setVolume(this.volume); + Logger.info(`Invoke setVolume ${this.volume} succeeded.`); + } catch (err) { + Logger.error(`Invoke setVolume failed, code is ${err.code}, message is ${err.message}.`); + } + } else { + Logger.error('Audio loopback not created.'); + } + } + + build() { + Column({ space: 20 }) { + Text($r('app.string.KARAOKE_DEMO_TITLE')).fontSize(24).fontWeight(FontWeight.Bold).padding(16).id('karaoke_title') + + Text(this.statusText).fontSize(16).padding(8).id('karaoke_status_text') + + Button($r('app.string.KARAOKE_INIT_BUTTON')) + .onClick(() => this.initLoopback()) + .width('80%').margin(8) + .id('karaoke_init_btn') + + Button($r('app.string.KARAOKE_ENABLE_BUTTON')) + .onClick(() => this.enableLoopback()) + .width('80%').margin(8) + .id('karaoke_enable_btn') + + Button($r('app.string.KARAOKE_DISABLE_BUTTON')) + .onClick(() => this.disableLoopback()) + .width('80%').margin(8) + .id('karaoke_disable_btn') + + Text($r('app.string.KARAOKE_VOLUME_LABEL')).fontSize(16).margin({ top: 24 }).id('karaoke_volume_label') + + Slider({ + value: this.volume, + min: 0, + max: 100, + step: 1, + style: SliderStyle.OutSet + }) + .width('80%') + .onChange((val: number) => this.setVolume(val)) + .id('karaoke_volume_slider') + } + .padding(20) + .width('100%') + .height('100%') + .backgroundColor('#F9F9F9') + .alignItems(HorizontalAlign.Center) + .justifyContent(FlexAlign.Start) + } +} + diff --git a/code/BasicFeature/Media/Audio/entry/src/main/resources/base/media/png01_Karaoke.png b/code/BasicFeature/Media/Audio/entry/src/main/resources/base/media/png01_Karaoke.png new file mode 100644 index 0000000000000000000000000000000000000000..4beae5746b11d45e8dbff65d6cc94ddb9703ef62 Binary files /dev/null and b/code/BasicFeature/Media/Audio/entry/src/main/resources/base/media/png01_Karaoke.png differ diff --git a/code/BasicFeature/Media/Audio/entry/src/main/resources/base/profile/main_pages.json b/code/BasicFeature/Media/Audio/entry/src/main/resources/base/profile/main_pages.json index 9b894ad75007050a84cbb9be1b0735cc2c7a6bc0..cbf90e5726e2a7c11a5dc44c4738e10d03e8eadd 100644 --- a/code/BasicFeature/Media/Audio/entry/src/main/resources/base/profile/main_pages.json +++ b/code/BasicFeature/Media/Audio/entry/src/main/resources/base/profile/main_pages.json @@ -9,6 +9,7 @@ "pages/ParallelCapturer", "pages/LiveCapturer", "pages/VolumePanel", - "pages/SpatialAudio" + "pages/SpatialAudio", + "pages/Karaoke" ] } diff --git a/code/BasicFeature/Media/Audio/entry/src/ohosTest/ets/test/Ability.test.ets b/code/BasicFeature/Media/Audio/entry/src/ohosTest/ets/test/Ability.test.ets index 3ab83cfc34bc5ef4ac6791d86d72f46e34316fbd..b9da8684b85368bdb1f0abc3d1223731b11a768e 100644 --- a/code/BasicFeature/Media/Audio/entry/src/ohosTest/ets/test/Ability.test.ets +++ b/code/BasicFeature/Media/Audio/entry/src/ohosTest/ets/test/Ability.test.ets @@ -1075,5 +1075,89 @@ export default function abilityTest() { Logger.info(TAG, BUNDLE + 'SpatialAudio_002 end'); done(); }) + + /** + * [Karaoke] UI Component Rendering Test + */ + it(TAG + 'KaraokePage_UI_001', 0, async (done: Function) => { + Logger.info(TAG, 'KaraokePage_UI_001 begin'); + try { + await driver.delayMs(1000); + + Logger.info(TAG, 'Check status text'); + await driver.assertComponentExist(ON.id('karaoke_status_text')); + + Logger.info(TAG, 'Check Init button'); + await driver.assertComponentExist(ON.id('karaoke_init_btn')); + + Logger.info(TAG, 'Check Enable button'); + await driver.assertComponentExist(ON.id('karaoke_enable_btn')); + + Logger.info(TAG, 'Check Disable button'); + await driver.assertComponentExist(ON.id('karaoke_disable_btn')); + + Logger.info(TAG, 'Check Slider component'); + await driver.assertComponentExist(ON.id('karaoke_volume_slider')); + } catch (err) { + Logger.error(TAG, 'KaraokePage_UI_001 failed: ' + JSON.stringify(err)); + expect(false).assertTrue(); + } + Logger.info(TAG, 'KaraokePage_UI_001 end'); + done(); + }); + + /** + * [Karaoke] Initialize audio loopback and check state + */ + it(TAG + 'KaraokePage_Init_002', 0, async (done: Function) => { + Logger.info(TAG, 'KaraokePage_Init_002 begin'); + try { + let initBtn = await driver.findComponent(ON.id('karaoke_init_btn')); + await initBtn.click(); + await driver.delayMs(1000); + await driver.assertComponentExist(ON.id('karaoke_status_text')); + } catch (err) { + Logger.error(TAG, 'KaraokePage_Init_002 failed: ' + JSON.stringify(err)); + expect(false).assertTrue(); + } + Logger.info(TAG, 'KaraokePage_Init_002 end'); + done(); + }); + + /** + * [Karaoke] Enable loopback and verify running state + */ + it(TAG + 'KaraokePage_Enable_003', 0, async (done: Function) => { + Logger.info(TAG, 'KaraokePage_Enable_003 begin'); + try { + let enableBtn = await driver.findComponent(ON.id('karaoke_enable_btn')); + await enableBtn.click(); + await driver.delayMs(1500); + await driver.assertComponentExist(ON.id('karaoke_status_text')); + } catch (err) { + Logger.error(TAG, 'KaraokePage_Enable_003 failed: ' + JSON.stringify(err)); + expect(false).assertTrue(); + } + Logger.info(TAG, 'KaraokePage_Enable_003 end'); + done(); + }); + + /** + * [Karaoke] Disable loopback and verify idle state + */ + it(TAG + 'KaraokePage_Disable_004', 0, async (done: Function) => { + Logger.info(TAG, 'KaraokePage_Disable_004 begin'); + try { + let disableBtn = await driver.findComponent(ON.id('karaoke_disable_btn')); + await disableBtn.click(); + await driver.delayMs(1000); + await driver.assertComponentExist(ON.id('karaoke_status_text')); + } catch (err) { + Logger.error(TAG, 'KaraokePage_Disable_004 failed: ' + JSON.stringify(err)); + expect(false).assertTrue(); + } + Logger.info(TAG, 'KaraokePage_Disable_004 end'); + done(); + }); }) } \ No newline at end of file diff --git a/code/BasicFeature/Media/Audio/screenshots/device/index.png b/code/BasicFeature/Media/Audio/screenshots/device/index.png index 4b98e2f706793bbb146d67e3b6aa3d48107e661a..ee11fb086ae08e7cf34b78a5c357ca96cb2e1a9d 100644 Binary files a/code/BasicFeature/Media/Audio/screenshots/device/index.png and b/code/BasicFeature/Media/Audio/screenshots/device/index.png differ diff --git a/code/BasicFeature/Media/Audio/screenshots/device/karaoke.jpeg b/code/BasicFeature/Media/Audio/screenshots/device/karaoke.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..157343eec95aef90747838ef1c625040017e97c8 Binary files /dev/null and b/code/BasicFeature/Media/Audio/screenshots/device/karaoke.jpeg differ