H5 微信小遊戲 —— 音訊管理器
阿新 • • 發佈:2019-01-03
前言
原本使用的是 egret 的 egret.SoundChannel
和 egret.Sound
來管理音訊,但不知為何在重複將遊戲切換到前後臺後,很容易出現背景音播放不全、播放重複和無法播放的問題,懶得再去排查原因乾脆使用小遊戲提供的音訊播放 API 重寫了一個音訊管理工具。
API 相關
參考官方文件 ,大致瞭解音訊播放是利用微信介面 建立 InnerAudioContext 物件作為唯一實現方式,此外,同時播放的音訊例項在 Android 平臺上有 10 個的上限限制,對於不再使用的音訊例項可以通過 介面銷燬。
大致思路
管理器可以設計為單例模式,其中包含:
一個播放音效的音訊例項池,並設定池子的容量為 9 (留一個給背景音樂播放使用);
一個單獨用於播放背景音樂的音訊例項(不放入池內管理)
建立管理器時可以預建立少量例項在例項池中,每次播放音效的步驟:
從池內獲取可用的(狀態 paused == true)播放例項;
當例項用完但還未達到池子容量時,可建立新的例項並放在池內;
當無可用且池子已滿時,停掉最早建立的音訊例項,用於播放新的音訊(視音效的重要性的選擇,以新音效為重)。
為了防止每次都重新下載音訊,需要結合資源快取機制,即播放一個新的音訊時,先將音訊下載到本地,再將本地檔案地址賦值給
.src
屬性,再播放音訊。
實現程式碼
class WeChatSoundManager { private static _instance: WeChatSoundManager; public static get instance() { if (!this._instance) { this._instance = new WeChatSoundManager(); } return this._instance; } private _bgmCtx = null; private _bgmPath = ""; private soundCtxPool = new Array<any>(); // 預建立例項private PreBuildNum = 3; private MaxSoundNum = 10; // 例項使用指標 private _index = 0; public constructor() { let soundCtx = null; for (let i = 0; i++; i < this.PreBuildNum) { soundCtx = wx.createInnerAudioContext(); this.soundCtxPool.push(soundCtx); } } public clean() { this.soundCtxPool = new Array<any>(); this._index = 0; this._bgmCtx = null; this._bgmPath = ""; } // 播放背景音樂 public playBGM(bgmPath: string) { //if (gGame.chanelType != ChanelType.WXMini) return; gLog('背景音樂播放地址:' + bgmPath); if (this._bgmPath == bgmPath && this._bgmCtx != null) { this._bgmCtx.play(); } else { this._bgmPath = bgmPath; if (!this._bgmCtx) { this._bgmCtx = wx.createInnerAudioContext(); } this._bgmCtx.src = bgmPath; this._bgmCtx.loop = true; this._bgmCtx.autoplay = true; } } // 背景音樂是否正在播放 public isBGMPlaying() { if (this._bgmCtx) { return !this._bgmCtx.paused; } return false; } // 暫停背景音樂 public pauseBGM() { if (this._bgmCtx) { this._bgmCtx.pause(); } } // 恢復背景音樂 public resumeBGM() { if (this._bgmCtx) { this._bgmCtx.play(); } else { if (this._bgmPath) { this.playBGM(this._bgmPath); } } } // 停止背景音樂 public stopBGM() { if (this._bgmCtx) { this._bgmCtx.stop(); this._bgmCtx.destroy(); this._bgmCtx = null; } } // 播放音訊 public playSound(soundPath: string) { //if (gGame.chanelType != ChanelType.WXMini) return; gLog('音訊播放地址:' + soundPath); let soundCtx = this.getSoundCtx(); if (soundCtx) { soundCtx.src = soundPath; soundCtx.stop(); soundCtx.play(); } } public stopAllSound() { this.soundCtxPool.forEach(soundCtx => { // 暫停或停止了 if (!soundCtx.paused) { soundCtx.stop(); } }); } // 獲取一個音訊例項 private getSoundCtx() { let soundCtx = null; for (let i = 0; i++; i < this.soundCtxPool.length) { soundCtx = this.soundCtxPool[i]; // 暫停或停止了 if (soundCtx.paused) { return soundCtx; } } if (!soundCtx) { // 留一個例項用於播放背景音樂 if (this.soundCtxPool.length < this.MaxSoundNum - 1) { soundCtx = wx.createInnerAudioContext(); } else { soundCtx = this.soundCtxPool[this._index]; this._index++; if (this._index > this.MaxSoundNum) { this._index = 0; } soundCtx.stop(); } } return soundCtx; } }
呼叫方式:
WeChatSoundManager.instance.playBGM(url);// 播放背景音樂 WeChatSoundManager.instance.playSound(url);// 播放普通音效
其他
在手機有外部事件需要暫停音訊和事件結束恢復音訊播放,分別通過遊戲中全域性監聽 wx.onAudioInterruptionBegin
和 wx.onAudioInterruptionEnd
兩個事件來實現,當然為了方便和防止重複建立,可以將其寫在音訊管理器的構建方法中,如下:
public constructor() { let soundCtx = null; for (let i = 0; i++; i < this.PreBuildNum) { soundCtx = wx.createInnerAudioContext(); this.soundCtxPool.push(soundCtx); } // 中斷事件開始 wx.onAudioInterruptionBegin(() => { if (this.musicEnabled) { gLog('------------ 暫停背景音樂'); this.pauseBGM(); } }); // 中斷事件結束 wx.onAudioInterruptionEnd(() => { if (this.musicEnabled) { gLog('------------ 恢復背景音樂'); this.resumeBGM(); } }); }