1. 程式人生 > >H5 微信小遊戲 —— 音訊管理器

H5 微信小遊戲 —— 音訊管理器

前言

原本使用的是 egret 的 egret.SoundChannelegret.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.onAudioInterruptionBeginwx.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();
            }
        });
    }

參考