鸿蒙Cordova开发踩坑记录:音频焦点的“独占欲“

摘要:在 Web 游戏开发中,背景音乐(BGM)是灵魂。但我们发现应用切到后台后,BGM 依然在播放;或者当电话打进来时,游戏音效和通话声音混在一起。这是因为 Web Audio API 默认并不由系统音频焦点管理器(Audio Focus Manager)自动接管。本文讲解如何手动管理音频焦点,做一个"有礼貌"的 App。

🎵 1. 噪音污染

默认情况下,HTML5 的 <audio>Web Audio Context 仅仅是向音频输出设备写入数据。

表现

  1. 后台播放:用户按下 Home 键回到桌面,游戏 BGM 还在响,用户必须杀掉后台进程才能停止。
  2. 混音灾难:用户打开网易云音乐听歌,打开我们的游戏,两首歌同时响。

🎧 2. 鸿蒙音频管理机制

HarmonyOS 的音频策略是基于 Session(会话)Focus(焦点) 的。

  • 当应用需要发声时,必须申请焦点。
  • 当其他高优先级应用(如电话、闹钟)申请焦点时,我们会收到"打断"事件。

Webview 并不会自动帮我们把这些系统事件映射到 JS 层。

🛠️ 3. 桥接实现

我们需要在 Native 层监听系统音频状态,并通知 Web 层暂停/恢复播放。

3.1 Native 监听器 (ArkTS)

使用 audio 模块管理焦点。

typescript 复制代码
import audio from '@ohos.multimedia.audio';

export class AudioFocusManager {
    private audioManager = audio.getAudioManager();
    private webController: WebviewController;

    constructor(controller: WebviewController) {
        this.webController = controller;
        this.initListener();
    }

    initListener() {
        // 监听音频打断事件
        this.audioManager.on('interrupt', (interruptEvent) => {
            if (interruptEvent.forceType === audio.InterruptForceType.INTERRUPT_FORCE) {
                // 系统强制打断(如来电)
                this.pauseWebAudio();
            } else if (interruptEvent.hintType === audio.InterruptHintType.INTERRUPT_HINT_PAUSE) {
                // 建议暂停
                this.pauseWebAudio();
            } else if (interruptEvent.hintType === audio.InterruptHintType.INTERRUPT_HINT_RESUME) {
                // 恢复播放
                this.resumeWebAudio();
            }
        });
    }

    pauseWebAudio() {
        console.info("Suspending Web Audio...");
        this.webController.runJavaScript("window.AudioManager.suspend()");
    }

    resumeWebAudio() {
        console.info("Resuming Web Audio...");
        this.webController.runJavaScript("window.AudioManager.resume()");
    }
}

3.2 Web 端封装

在 JS 端,我们需要一个全局管理器来控制所有声音。

javascript 复制代码
window.AudioManager = {
    context: new (window.AudioContext || window.webkitAudioContext)(),
    isInterrupted: false,

    suspend: function() {
        if (this.context.state === 'running') {
            this.context.suspend().then(() => {
                this.isInterrupted = true;
                console.log("Audio Context suspended by system");
            });
        }
        // 同时暂停所有 <audio> 标签
        document.querySelectorAll('audio').forEach(el => el.pause());
    },

    resume: function() {
        if (this.isInterrupted) {
            this.context.resume().then(() => {
                this.isInterrupted = false;
                console.log("Audio Context resumed by system");
            });
            // 恢复 BGM(音效通常不需要恢复)
            const bgm = document.getElementById('bgm');
            if (bgm) bgm.play();
        }
    }
};

3.3 生命周期联动

除了音频焦点,还要处理应用的前后台切换(Lifecycle)。

EntryAbility.ets 中:

typescript 复制代码
onForeground() {
    // 应用回到前台,恢复音频
    // 通过 EventHub 通知 Web 组件调用 window.AudioManager.resume()
}

onBackground() {
    // 应用退到后台,暂停音频
    // 必须暂停,否则可能导致应用被系统判定为"后台高耗电"而强杀
}

🔍 4. 遇到的坑

自动播放策略 (Autoplay Policy)

即便是恢复播放,浏览器也要求必须由"用户手势"触发。但在 resume() 方法中,这是一个系统回调,不是用户点击。
解决 :Web Audio Context 一旦在第一次点击中被成功激活(Created),后续的 suspend/resume 不再受 Autoplay 策略限制。所以务必引导用户进入游戏时点击一下"开始"按钮来初始化音频上下文。

📜 5. 总结

做一个有素质 App 的自我修养:

  1. 退后台必静音
  2. 来电必暂停
  3. 恢复要智能(不要恢复早已播放结束的短音效)。

通过 Native 监听 + JS 执行,我们完美解决了 Web 游戏的音频管家问题。

相关推荐
Huang兄2 小时前
鸿蒙-List和Grid拖拽排序:仿微信小程序删除效果
harmonyos·arkts·arkui
anyup20 小时前
🔥2026最推荐的跨平台方案:H5/小程序/App/鸿蒙,一套代码搞定
前端·uni-app·harmonyos
Ranger09291 天前
鸿蒙开发新范式:Gpui
rust·harmonyos
Huang兄1 天前
鸿蒙-深色模式适配
harmonyos·arkts·arkui
SummerKaze3 天前
为鸿蒙开发者写一个 nvm:hmvm 的设计与实现
harmonyos
在人间耕耘5 天前
HarmonyOS Vision Kit 视觉AI实战:把官方 Demo 改造成一套能长期复用的组件库
人工智能·深度学习·harmonyos
王码码20355 天前
Flutter for OpenHarmony:socket_io_client 实时通信的事实标准(Node.js 后端的最佳拍档) 深度解析与鸿蒙适配指南
android·flutter·ui·华为·node.js·harmonyos
HarmonyOS_SDK5 天前
【FAQ】HarmonyOS SDK 闭源开放能力 — Ads Kit
harmonyos
Swift社区5 天前
如何利用 ArkUI 框架优化鸿蒙应用的渲染性能
华为·harmonyos
特立独行的猫a5 天前
uni-app x跨平台开发实战:开发鸿蒙HarmonyOS影视票房榜组件完整实现过程
华为·uni-app·harmonyos·轮播图·uniapp-x