Cocos2d-JS HTML5的cc.audioEngine音乐音效在iOS设备上不响应静音键的问题

之前写过一篇同样关于Cocos2d-JS H5的音频播放相关的文章: DOMException: play() can only be initiated by a user gesture(Cocos2d-JS HTML5),其中提到了移动端上H5的声音播放需要在用户操作后且必须在操作后一定时间内的问题,这次记录的问题也与此问题有关,直接现象为Cocos2d-JS(我用的3.13 lite版,通过js-builder获取:http://www.cocos2d-x.org/filecenter/jsbuilder,本文所描述问题可能仅与此版本相关!)的H5游戏在iOS设备上播放的音乐音效不受静音键影响,无法根据静音键静音!
做过iOS音频相关原生开发的应该知道,iOS通过对AVAudioSession的Category设置来决定应用的音频播放所属的分类,从而决定是否受静音键的影响,比如,静音开关开启的时候,短信音,Push消息提示音之类的会不播放,但优酷视频,虾米音乐之类的应用的音频还是会播放,不受影响,具体参考如下:

会话类型 说明 是否要求输入 是否要求输出 是否遵从静音键
AVAudioSessionCategoryAmbient 混音播放,可以与其他音频应用同时播放
AVAudioSessionCategorySoloAmbient 独占播放
AVAudioSessionCategoryPlayback 后台播放,独占
AVAudioSessionCategoryRecord 录音模式
AVAudioSessionCategoryPlayAndRecord 播放和录音,此时可以录音也可以播放
AVAudioSessionCategoryAudioProcessing 硬件解码音频,此时不能播放和录制
AVAudioSessionCategoryMultiRoute 多种输入输出,例如可以耳机、USB设备同时播放

 


而实际上HTML5上如果是实现音乐视频播放器的话,应该不响应的静音键,但要是游戏的BGM或者音效的话显然还是应该响应静音键开关的,这点比如最近开放的Facebook Instant Game的发布checklist里就提到了:

Respect the physical mute switch on Mobile devices. We recommend using the WebAudio API for that.

而实际测试发现通过cc.audioEngine.playMusic播放的BGM并不会响应静音键,而且仔细观察后发现,这种方式播放的游戏背景音乐会被当做音乐播放器播放的音乐处理,在iOS的下拉菜单中能看到有音乐在播放,如下图:

显然,这样的话就不属于响应静音键的AVAudioSessionCategory了,查询WebAudio的以及一些example、其它H5游戏后发现,正如上面FB的checklist里提到的,通过WebAudio方式播放的音频是可以响应静音键的,即也不会作为iOS系统的音乐播放器音频播放。查看cocos2d-js-v3.13.js引擎代码中发现是有WebAudio支持的,并且在iOS设备上各种艰难的查log后发现WebAudio的支持也正确被启用了,省略中途若干其它调试读引擎源码的过程,最终发现问题所在:
cc.audioEngine中的playEffect实现里有这么一段:

playEffect: function(url, loop){
...
audio = cc.loader.getRes(url);
            if (audio && SWA && audio._AUDIO_TYPE === 'AUDIO') {
                cc.loader.release(url);
                audio = null;
            }
            if (audio) {
                if (SWA && audio._AUDIO_TYPE === 'AUDIO') {
                    loader.loadBuffer(url, function (error, buffer) {
                        audio.setBuffer(buffer);
                        audio.setVolume(cc.audioEngine._effectVolume);
                        if (!audio.getPlaying())
                            audio.play(0, loop || false);
                    });
                } else {
                    audio = audio.cloneNode();
                    audio.setVolume(this._effectVolume);
                    audio.play(0, loop || false);
                    effectList.push(audio);
                    return audio;
                }
            }
loader.useWebAudio = true;
            cc.loader.load(url, function (audio) {
                audio = cc.loader.getRes(url);
                audio = audio.cloneNode();
                audio.setVolume(cc.audioEngine._effectVolume);
                audio.play(0, loop || false);
                effectList.push(audio);
            });
            loader.useWebAudio = false;
            return audio;
        },

可以看到,这个playEffect的逻辑为cc.loader.getRes取不到已加载的audio,且WebAudio启用,audio类型不是’AUDIO’(这应该是现在不响应静音键的HTML AUDIO播放方式)的情况下,开启loader的useWebAudio来以WebAudio方式加载指定audio。这段WebAudio加载为关键逻辑,如果使用cc.LoaderScene或者直接cc.loader进行音频preload,甚至是通过playMusic内的load代码,如下:

playMusic: function(url, loop){
            var bgMusic = this._currMusic;
            if (bgMusic && bgMusic.getPlaying()) {
                bgMusic.stop();
            }
            var audio = cc.loader.getRes(url);
            if (!audio) {
                cc.loader.load(url);
                audio = cc.loader.getRes(url);
            }
            audio.setVolume(this._musicVolume);
            audio.play(0, loop || false);
            this._currMusic = audio;
        },

均不会以WebAudio方式生成audio,因此,现在的bgm并不会响应静音键!暂时的解决方法是去掉所有音频的preload,并且所有bgm类音频与音效一样使用playEffect播放,修改后测试音频已可以正常响应静音键。

PS: 开头提到本文记录的问题和之前写过的移动端用户操作后才能播音频的问题有关,这部分情况是这样,在检查WebAudio的过程中,发现Cocos2d-JS引擎实际上是处理了这个问题的,具体可参考cc.Audio.play:

play: function (offset, loop) {
        if (!this._element) return;
        this._element.loop = loop;
        this._element.play();
        if (this._AUDIO_TYPE === 'AUDIO' && this._element.paused) {
            this.stop();
            cc.Audio.touchPlayList.push({ loop: loop, offset: offset, audio: this._element });
        }
        if (cc.Audio.bindTouch === false) {
            cc.Audio.bindTouch = true;
            cc.game.canvas.addEventListener('touchstart', cc.Audio.touchStart);
        }
    },

可以看到,引擎通过加入touchPlayList以及监听canvas的触摸事件来延迟到下一次触摸操作后自动播放touchPlayList里的音频,从而绕开移动端上的这个限制的,所以也可以直接把这个问题交给引擎处理,但貌似仅限于通过playEffect播放。

博主友情提示:

如您在评论中需要提及如QQ号、电子邮件地址或其他隐私敏感信息,欢迎使用>>博主专用加密工具v2<<处理后发布,原文只有博主可以看到。

如果本博对您有帮助,请领个支付宝红包免费支持我下,谢谢!