鸿蒙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 游戏的音频管家问题。

相关推荐
youyin3 天前
HarmonyOS ArkUI 组件与自定义组件零基础:从搭页面到组件化开发
华为·harmonyos
HwJack203 天前
【HarmonyOS开发小实践】ArkTS 从 TypeScript 到方舟语言的演进
华为·harmonyos
张星河zen3 天前
4096卡、8EFLOPS、7.2T NPO:华为昇腾960超节点详解
华为·ai训练·ai推理·超节点·npo·昇腾960·hi-one
云运维笔记3 天前
华为设备IP地址配置全攻略
运维·网络·计算机网络·华为
威哥爱编程3 天前
HarmonyOS 7 星盾机密风控实战:设备风险因子端侧融合计算,可用不可见
harmonyos·arkts
威哥爱编程3 天前
HarmonyOS 7 应用快启实战:关键资源预加载进内存,冷启告别白屏
华为·harmonyos·arkts
马剑威(威哥爱编程)3 天前
【共创稿事节】HarmonyOS 7 视觉 AI 进阶实战:人脸检测 + 通用文字识别(OCR)两步接入
华为·harmonyos·arkts
HMS Core4 天前
HarmonyOS智慧多窗,让应用在任意窗口都“恰到好处”
harmonyos
梦想不只是梦与想4 天前
鸿蒙 应用发布准备工作(一)
harmonyos·鸿蒙上架·鸿蒙应用发布