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

相关推荐
柒儿吖1 小时前
Electron for 鸿蒙PC - Native模块Mock与降级策略
javascript·electron·harmonyos
用户463989754322 小时前
Harmony os——AbilityStage 组件管理器:我理解的 Module 级「总控台」
harmonyos
用户463989754323 小时前
Harmony os——UIAbility 组件基本用法:启动页、Context、终止与拉起方信息全流程
harmonyos
用户463989754323 小时前
Harmony os——启动应用内的 UIAbility:跨 Ability 跳转、回传结果 & 指定页面全流程
harmonyos
用户463989754323 小时前
Harmony os——UIAbility 组件生命周期|我按自己的理解梳了一遍
harmonyos
汉堡黄4 小时前
鸿蒙开发:案例集合Tabs:自定义tabs突出(凸出)球体左右跟随滑动动画
harmonyos
Q***l6874 小时前
HarmonyOS在智能穿戴中的Huawei Watch
华为·harmonyos
●VON5 小时前
Flutter 项目成功运行后,如何正确迁移到 OpenHarmony?常见疑问与跳转失效问题解析
flutter·华为·openharmony·开源鸿蒙
todoitbo5 小时前
基于MCP架构的DevUI多组件协作实践:打造智能业务分析平台
华为·ai·架构·devui·matechat