鸿蒙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 模块管理焦点。

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 端,我们需要一个全局管理器来控制所有声音。

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 中:

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

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

淼学派对

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值