音频焦点:多应用音频播放的抢占与混音(193)
在鸿蒙HarmonyOS应用开发中当多个应用或同一应用内的多个音频流同时请求发声时为了避免声音混乱系统提供了一套完善的音频焦点Audio Focus管理机制。一、 核心机制与系统默认策略音频焦点本质上是一个“请求-授权-监听”的仲裁机制。应用在播放音频前需向系统声明自己的音频类型StreamUsage系统根据类型优先级和启动顺序自动决策中断策略。四种默认中断策略终止Stop后播音频强制先播音频停止且先播音频在后续不会自动恢复如播放视频打断听书。暂停Pause后播音频强制先播音频暂停待后播音频结束后先播音频自动恢复如来电打断音乐。降音Duck后播音频与先播音频并发但先播音频音量被降低如导航语音打断音乐。并发Mix两个音频流互不影响同时并发播放如游戏音效与背景音乐。焦点模式InterruptMode针对同一应用创建的多个音频流开发者可选择共享焦点模式SHARE_MODE应用自主管控内部并发默认或独立焦点模式INDEPENDENT_MODE每个音频流独立拥有焦点受系统统一管控。// AudioStreamConfig.ets import { audio } from kit.AudioKit; export class AudioStreamConfig { // 1. 正确配置音频流类型StreamUsage public static getMusicStreamInfo(): audio.AudioStreamInfo { return { samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_44100, channels: audio.AudioChannel.CHANNEL_STEREO, sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE, encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW }; } // 音乐播放配置 public static getMusicRendererInfo(): audio.AudioRendererInfo { return { usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 音乐类型 rendererFlags: 0 }; } // 导航语音配置触发降音 Duck 效果 public static getNavRendererInfo(): audio.AudioRendererInfo { return { usage: audio.StreamUsage.STREAM_USAGE_NAVIGATION, // 导航类型 rendererFlags: 0 }; } }二、 自定义焦点策略AudioSession当系统默认的焦点策略无法满足特定业务需求时开发者可通过AudioSessionManager激活自定义的音频会话AudioSession实现精细化管控。四种自定义并发模式CONCURRENCY_DEFAULT遵循系统默认策略。CONCURRENCY_MIX_WITH_OTHERS强制与其他音频流并发混音。CONCURRENCY_DUCK_OTHERS强制降低其他音频流的音量并并发。CONCURRENCY_PAUSE_OTHERS强制暂停其他音频流待释放焦点后通知其恢复。特殊场景处理对于静音播放或短音播放系统提供了setSilentModeAndMixWithOthers或SoundPool的默认并发模式确保在静音阶段或播放极短提示音时不打断后台正在播放的音乐。// CustomAudioSession.ets import { audio } from kit.AudioKit; import { BusinessError } from kit.BasicServicesKit; export class CustomAudioSession { private audioSessionManager: audio.AudioSessionManager; constructor() { const audioManager audio.getAudioManager(); this.audioSessionManager audioManager.getSessionManager(); } // 激活自定义并发策略 public async activateCustomStrategy(mode: audio.AudioConcurrencyMode) { const strategy: audio.AudioSessionStrategy { concurrencyMode: mode // 可选: CONCURRENCY_DEFAULT, CONCURRENCY_MIX_WITH_OTHERS, // CONCURRENCY_DUCK_OTHERS, CONCURRENCY_PAUSE_OTHERS }; try { await this.audioSessionManager.activateAudioSession(strategy); console.info(AudioSession activated successfully); } catch (err) { const error err as BusinessError; console.error(Failed to activate AudioSession: ${error.message}); } } // 停用自定义音频会话 public async deactivateCustomStrategy() { try { await this.audioSessionManager.deactivateAudioSession(); } catch (err) { console.error(Failed to deactivate AudioSession); } } }三、 焦点事件监听与状态同步当焦点发生变化时系统会下发InterruptEvent应用必须正确响应以维持 UI 状态与底层播放状态的一致性。强制中断INTERRUPT_FORCE系统已强制执行了暂停、停止或降音操作。应用收到事件后无需再次调用播放器的暂停/停止接口但必须更新自身的 UI 状态如将播放按钮切换为暂停图标。共享中断INTERRUPT_SHARE系统仅给出建议由应用自行决定是否响应。例如收到INTERRUPT_HINT_RESUME时应用可根据当前业务逻辑决定是否恢复播放。资源释放与焦点回收当音频流调用pause、stop或release时系统会自动释放焦点。若存在被该音频流压低音量或暂停的其他音频流系统将自动触发它们的恢复操作。// AudioFocusListener.ets import { audio } from kit.AudioKit; export class AudioFocusListener { private audioSessionManager: audio.AudioSessionManager; constructor() { const audioManager audio.getAudioManager(); this.audioSessionManager audioManager.getSessionManager(); } // 监听音频会话停用事件焦点被抢占或超时 public startListening(onFocusLost: (reason: string) void) { this.audioSessionManager.on(audioSessionDeactivated, (event: audio.AudioSessionDeactivatedEvent) { console.info(AudioSession deactivated, reason: ${event.reason}); // 强制中断处理系统已暂停/停止底层音频此处仅同步 UI 状态 onFocusLost(焦点丢失: ${event.reason}); } ); } // 取消监听 public stopListening() { this.audioSessionManager.off(audioSessionDeactivated); } }四、 性能优化正确配置 StreamUsage音频流类型直接决定了默认焦点行为。例如导航语音必须配置为STREAM_USAGE_NAVIGATION以实现降音Duck效果若错误配置为STREAM_USAGE_MUSIC则会直接终止后台音乐。避免焦点泄漏在页面销毁或业务结束时务必确保调用了deactivateAudioSession()或释放了AVPlayer/AudioRenderer否则会导致后台焦点被长期占用其他应用无法正常发声。防抖与状态一致性在收到INTERRUPT_HINT_DUCK时虽然系统已自动降低音量但应用层若有自定义的 UI 音量指示器也应同步更新避免视觉与听觉体验割裂。// SafeAudioPlayer.ets import { audio } from kit.AudioKit; import { CustomAudioSession } from ./CustomAudioSession; import { AudioFocusListener } from ./AudioFocusListener; export class SafeAudioPlayer { private renderer: audio.AudioRenderer | null null; private customSession: CustomAudioSession; private focusListener: AudioFocusListener; constructor() { this.customSession new CustomAudioSession(); this.focusListener new AudioFocusListener(); } // 安全初始化与播放 public async initAndPlay() { // 1. 激活自定义策略 await this.customSession.activateCustomStrategy( audio.AudioConcurrencyMode.CONCURRENCY_DUCK_OTHERS ); // 2. 监听焦点丢失 this.focusListener.startListening((reason) { console.warn(UI 同步: ${reason}更新播放按钮为暂停状态); }); // 3. 创建并启动渲染器 // this.renderer await audio.createAudioRenderer(...); // await this.renderer.start(); } // 核心避坑组件销毁时的安全释放防止焦点泄漏 public async safeRelease() { // 1. 取消焦点监听 this.focusListener.stopListening(); // 2. 停用自定义 AudioSession await this.customSession.deactivateCustomStrategy(); // 3. 释放底层音频渲染器 if (this.renderer) { await this.renderer.stop(); await this.renderer.release(); this.renderer null; } } }五、 音频焦点实战自定义并发策略与中断处理在实际业务中开发者往往需要超越系统默认的焦点行为例如实现游戏音效与背景音乐的完美混音或处理来电等系统级强制中断。AudioSession 自定义并发策略通过AudioSessionManager激活自定义会话开发者可以强制改变当前应用与其他应用的交互行为。例如在游戏场景中使用CONCURRENCY_MIX_WITH_OTHERS模式可确保游戏音效与后台音乐并发播放在语音播报场景中使用CONCURRENCY_DUCK_OTHERS模式可实现压低背景音乐的效果。监听并处理音频中断事件通过AVPlayer或AudioRenderer的on(audioInterrupt)接口监听中断事件。中断分为强制中断如来电和共享中断如其他应用请求播放。对于强制中断系统已接管底层音频流应用仅需同步 UI 状态如更新播放按钮图标对于共享中断的恢复提示应用可根据业务逻辑自主决定是否恢复播放。// AudioFocus实战.ets import { audio } from kit.AudioKit; Entry Component struct AudioFocusDemo { private audioManager audio.getAudioManager(); private sessionManager this.audioManager.getSessionManager(); private renderer: audio.AudioRenderer | null null; aboutToAppear() { this.initAudio(); } private async initAudio() { // 1. 自定义并发策略例如游戏音效混音 const strategy: audio.AudioSessionStrategy { concurrencyMode: audio.AudioConcurrencyMode.CONCURRENCY_MIX_WITH_OTHERS }; await this.sessionManager.activateAudioSession(strategy); // 2. 创建并配置渲染器 this.renderer await audio.createAudioRenderer({ streamInfo: { /* ... */ }, rendererInfo: { usage: audio.StreamUsage.STREAM_USAGE_GAME } }); // 3. 监听音频中断事件 this.renderer.on(audioInterrupt, (event: audio.InterruptEvent) { if (event.forceType audio.InterruptForceType.INTERRUPT_FORCE) { // 强制中断如来电系统已暂停仅同步 UI if (event.hintType audio.InterruptHint.INTERRUPT_HINT_PAUSE) { console.info(系统强制暂停更新 UI 为暂停状态); } } else if (event.forceType audio.InterruptForceType.INTERRUPT_SHARE) { // 共享中断自主决定是否恢复 if (event.hintType audio.InterruptHint.INTERRUPT_HINT_RESUME) { console.info(收到恢复建议根据业务逻辑决定是否恢复); // this.renderer?.start(); } } }); } aboutToDisappear() { // 安全释放 this.renderer?.release(); this.sessionManager.deactivateAudioSession(); } build() { Column() { /* UI */ } } }六、 多音频流协同与防泄漏机制复杂应用如视频编辑器、多音轨播放器通常包含多个音频流合理的焦点模式选择和资源管理是避免音频混乱的关键。共享焦点模式SHARE_MODE的应用对于同一应用内的多个音频流如视频原声与背景音乐推荐使用SHARE_MODE。在此模式下系统将该应用的所有音频流视为一个整体应用内部自主管理各流的音量比例与播放时序无需频繁向系统申请和释放焦点大幅降低开销。严格的资源释放与防泄漏音频焦点泄漏是导致“后台无声”或“多应用声音重叠”的常见元凶。在组件销毁aboutToDisappear或业务结束时必须确保执行以下清理操作若使用了自定义AudioSession必须调用deactivateAudioSession()释放焦点。必须调用AVPlayer或AudioRenderer的stop()和release()方法彻底释放底层音频资源。避免高频创建与销毁对于连续播放的短音效避免每次都创建和销毁AVPlayer。推荐使用SoundPool或将AudioRenderer配置为默认的并发模式在播放期间保持会话活跃仅在业务完全结束时统一释放。// MultiStreamManager.ets import { audio } from kit.AudioKit; export class MultiStreamManager { private renderer1: audio.AudioRenderer | null null; private renderer2: audio.AudioRenderer | null null; // 初始化多音频流使用共享焦点模式 public async init() { // 视频原声 this.renderer1 await audio.createAudioRenderer({ streamInfo: { /* ... */ }, rendererInfo: { usage: audio.StreamUsage.STREAM_USAGE_MUSIC } }); this.renderer1.setInterruptMode(audio.InterruptMode.SHARE_MODE); // 背景音乐 this.renderer2 await audio.createAudioRenderer({ streamInfo: { /* ... */ }, rendererInfo: { usage: audio.StreamUsage.STREAM_USAGE_GAME } }); this.renderer2.setInterruptMode(audio.InterruptMode.SHARE_MODE); } // 核心严格的资源释放防止焦点泄漏 public async safeRelease() { if (this.renderer1) { await this.renderer1.stop(); await this.renderer1.release(); this.renderer1 null; } if (this.renderer2) { await this.renderer2.stop(); await this.renderer2.release(); this.renderer2 null; } console.info(所有音频流已安全释放); } }