实现方案
当与其他应用的音频发生冲突,多个音频流同时播放时,系统预设了音频打断策略,对多音频的并发进行管控,只有持有音频焦点的音频流才可以正常播放,避免多个音频流无序并发播放的现象出现。为了维持应用和系统的状态一致性,保证符合用户直觉的交互体验,推荐应用监听音频打断事件,并在收到音频打断事件(InterruptEvent)时做出相应处理。对于视频类应用,被各种类型的其他应用音频打断场景和效果如下表所示:
被打断视频期望效果 | 参考打断效果 | 涉及的后起流应用音频类型
| 参考代码示例
|
打断暂停 | 后播应用播放时,先播应用暂停播放;后播应用停止播放后,先播应用恢复播放(音频停止后,如果存在对应的播放画面也建议将其停止)。 | 电话 | audioRenderer.on('audioInterrupt', async(interruptEvent: audio.InterruptEvent) => { if (interruptEvent.forceType === audio.InterruptForceType.INTERRUPT_FORCE) { // 强制打断类型(INTERRUPT_FORCE):音频相关处理已由系统执行,应用需更新自身状态,做相应调整 switch (interruptEvent.hintType) { case audio.InterruptHint.INTERRUPT_HINT_PAUSE: // 此分支表示系统已将音频流暂停(临时失去焦点),为保持状态一致,应用需切换至音频暂停状态 // 临时失去焦点:待其他音频流释放音频焦点后,本音频流会收到resume对应的音频打断事件,到时可自行继续播放 isPlay = false; // 此句为简化处理,代表应用切换至音频暂停状态的若干操作 break; default: break; } } }) // 订阅通话业务状态变化 import { observer } from '@kit.TelephonyKit'; observer.on('callStateChange', (data: observer.CallStateInfo) => { console.log("on callStateChange, data:" + JSON.stringify(data)); if(data.state == call.CallState.CALL_STATE_IDLE) { // 电话挂断后被暂停的音频流此时可以继续播放,建议应用继续播放,切换至音频播放状态 // 若应用此时不想继续播放,可以忽略此音频打断事件,不进行处理即可 // 继续播放,此处主动执行start(),以标识符变量started记录start()的执行结果 await audioRenderer.start().then(() => { started = true; // start()执行成功 }).catch((err: BusinessError) => { started = false; // start()执行失败 }); // 若start()执行成功,则切换至音频播放状态 if (started) { isPlay = true; // 此句为简化处理,代表应用切换至音频播放状态的若干操作 } else { // 音频继续播放执行失败 } } }); |
闹钟 | audioRenderer.on('audioInterrupt', async (interruptEvent: audio.InterruptEvent) => { if (interruptEvent.forceType === audio.InterruptForceType.INTERRUPT_FORCE) { // 强制打断类型(INTERRUPT_FORCE):音频相关处理已由系统执行,应用需更新自身状态,做相应调整 switch (interruptEvent.hintType) { case audio.InterruptHint.INTERRUPT_HINT_PAUSE: // 此分支表示系统已将音频流暂停(临时失去焦点),为保持状态一致,应用需切换至音频暂停状态 // 临时失去焦点:待其他音频流释放音频焦点后,本音频流会收到resume对应的音频打断事件,到时可自行继续播放 isPlay = false; // 此句为简化处理,代表应用切换至音频暂停状态的若干操作 break; default: break; } } else if (interruptEvent.forceType === audio.InterruptForceType.INTERRUPT_SHARE) { // 共享打断类型(INTERRUPT_SHARE):应用可自主选择执行相关操作或忽略音频打断事件 switch (interruptEvent.hintType) { case audio.InterruptHint.INTERRUPT_HINT_RESUME: // 此分支表示临时失去焦点后被暂停的音频流此时可以继续播放,建议应用继续播放,切换至音频播放状态 // 若应用此时不想继续播放,可以忽略此音频打断事件,不进行处理即可 // 继续播放,此处主动执行start(),以标识符变量started记录start()的执行结果 await audioRenderer.start().then(() => { started = true; // start()执行成功 }).catch((err: BusinessError) => { started = false; // start()执行失败 }); // 若start()执行成功,则切换至音频播放状态 if (started) { isPlay = true; // 此句为简化处理,代表应用切换至音频播放状态的若干操作 } else { // 音频继续播放执行失败 } break; default: break; } } }); |
铃声 |
VOIP 铃声(全屏呼叫/呼叫页面/横幅呼叫) |
VOIP 通话 |
VOIP MESSAGE(微信语音/畅联) |
打断停止 | 后播应用播放/录制时,先播应用停止播放;后播应用停止播放/录制后,先播应用不再恢复播放。 | 音乐 | audioRenderer.on('audioInterrupt', async (interruptEvent: audio.InterruptEvent) => { if (interruptEvent.forceType === audio.InterruptForceType.INTERRUPT_FORCE) { // 强制打断类型(INTERRUPT_FORCE):音频相关处理已由系统执行,应用需更新自身状态,做相应调整 switch (interruptEvent.hintType) { case audio.InterruptHint.INTERRUPT_HINT_STOP: // 此分支表示系统已将音频流停止(永久失去焦点),为保持状态一致,应用需切换至音频暂停状态 // 永久失去焦点:后续不会再收到任何音频打断事件,若想恢复播放,需要用户主动触发。 isPlay = false; // 此句为简化处理,代表应用切换至音频暂停状态的若干操作 break; default: break; } } }); |
视频 |
普通录音 |
音量压低 | 后播应用播放时,先播应用降低音量持续播放;后播应用停止播放后,先播应用恢复音量继续播放。 | 导航 | audioRenderer.on('audioInterrupt', async (interruptEvent: audio.InterruptEvent) => { if (interruptEvent.forceType === audio.InterruptForceType.INTERRUPT_FORCE) { // 强制打断类型(INTERRUPT_FORCE):音频相关处理已由系统执行,应用需更新自身状态,做相应调整 switch (interruptEvent.hintType) { case audio.InterruptHint.INTERRUPT_HINT_DUCK: // 此分支表示系统已将音频音量降低(默认降到正常音量的20%) isDucked = true; // 此句为简化处理,代表应用切换至降低音量播放状态的若干操作 break; case audio.InterruptHint.INTERRUPT_HINT_UNDUCK: // 此分支表示系统已将音频音量恢复正常 isDucked = false; // 此句为简化处理,代表应用切换至正常音量播放状态的若干操作 break; default: break; } } }); |
TextReader控件朗读语音 |
语音助手类短语音 |
并发播放 | 先播、后播应用并发混音播放。 | 游戏 | 此行为是系统默认行为,应用侧不需要适配。 |