木灵鱼儿

木灵鱼儿

阅读:66

最后更新:2022/09/20/ 16:52:19

使用howler.js封装一个网页游戏音乐管理器

踩坑指南

虽然howler.js说他可以自动感知然后自动播放,事实上没什么软用,所以我们还需要自己去配置一下触发器。监听一个全局的click事件之类的。

虽然howler.js说可以自动降级到h5播放,但是实际测试无法在安卓4.4中进行播放,但是个人测试,如果你使用一个audio元素,是可以播放的,但是这样就需要自己去手动封装一个基于audio元素的播放管理,项目赶的很,实在没有多余的时间去研究这个,特别是音效的播放,还是专业的库处理比较好,所以暂时对于安卓4.4的兼容是没法做到的。

官方对于这个问题也是很模糊,找不到解决办法。

howler.js在文档中找了半天,也没看到官方提供动态替换资源的方法,导致如果你有多个音效,就不得不new出多个howler实例对象。

代码

先安装插件:

npm i howler.js -D

github:howler.js

首先我声明一下ts类型:

types.ts

import { Howl } from "howler";
import { EffectName } from "./config";

/** audio基类 */
export interface BaseAudio {
  /** 播放的方法 */
  play(name: EffectName): void;

  /** 暂停的方法 */
  pause(): void;

  /** 继续播放 */
  continuePlay(): void;

  /** 静音 */
  mute(status: boolean): void;
}

/** 音效实例map */
export type EffectMap = Map<EffectName, Howl>;

声明了一个基类BaseAudio,后面写的bgm和effect类都是基于该基类进行实现,这样外部就可以依赖于这个基类。

然后就是一些音乐的配置文件,里面有音效的枚举,各种音乐的资源地址:

config.ts

/** bgm配置 */
export const bgmConfig = {
  src: "./audio/bgm.mp3",
};

/** 音效名称enum */
export enum EffectName {
  button = "button",
  number = "number",
  lose = "lose",
  win = "win",
  draw = "draw",
}

/** 音效配置:根据配置生成不同的音效实例 */
export const effectConfig = [
  {
    src: "./audio/button.mp3",
    name: EffectName.button,
  },
  {
    src: "./audio/number.mp3",
    name: EffectName.number,
  },
  {
    src: "./audio/lose.mp3",
    name: EffectName.lose,
  },
  {
    src: "./audio/win.mp3",
    name: EffectName.win,
  },
  {
    src: "./audio/draw.mp3",
    name: EffectName.draw,
  },
];

现在配置都有了,我们创建一个core文件夹存放bgm和effect类。

core/audioBgm.ts

import { Howl } from "howler";
import { bgmConfig } from "../config";
import { BaseAudio } from "../types";

class AudioBgm implements BaseAudio {
  /** 音乐实例 */
  private static bgm: Howl | null = null;
  /** 音乐配置 */
  private config = {
    // html5: true,  //开了会导致默认静音再开启无法播放
    autoplay: true,
    loop: true,
    volume: 1,
  };
  /** 音乐是否播放 */
  public get isPlay() {
    return AudioBgm.bgm?.playing() ?? false;
  }
  /** 是否已经监听了点击事件 */
  public static isListen = false;
  /** 是否静音 */
  private isMute = false; //true静音,false取消静音

  constructor() {
    if (!AudioBgm.bgm) {
      AudioBgm.bgm = new Howl({ ...this.config, ...bgmConfig });
    }
    //先触发play播放,因为知道是同一个浏览器页面,
    //地址的变化不会影响用户在浏览器中的操作变量变化(判断是否用户主动触发)
    this.play();
    //如果没有播放则添加一个click事件来手动触发
    //但是这个isPlay不是及时的,所以有可能过一会就播放了但是监听了事件
    //所以监听的事件里自己再做个判断
    if (!this.isPlay && !AudioBgm.isListen) {
      this.interactionClick = this.interactionClick.bind(this);
      this.listenClick();
    }
  }

  /** 监听点击事件播放音乐 */
  private listenClick() {
    document.addEventListener("click", this.interactionClick);
  }

  /** 点击事件,用于触发交互,无交互无法正确播放 */
  private interactionClick() {
    if (!this.isPlay) {
      this.play();
    }
    AudioBgm.isListen = true;
    /** 销毁自己 */
    document.removeEventListener("click", this.interactionClick);
  }

  /** 音乐播放 */
  public play(): void {
    if (this.isMute) return;
    AudioBgm.bgm?.play();
  }

  /** 暂停 */
  public pause(): void {
    AudioBgm.bgm?.pause();
  }

  /** 恢复播放 */
  public continuePlay(): void {
    this.play();
  }

  /** 静音 */
  public mute(status: boolean): void {
    AudioBgm.bgm?.mute(status);
    this.isMute = status;
    //不静音自动播放
    if (!this.isMute && !this.isPlay) {
      this.play();
    }
  }
}

export default AudioBgm;

core/audioEffect.ts

import { Howl } from "howler";
import { effectConfig, EffectName } from "../config";
import { BaseAudio, EffectMap } from "../types";

class AudioEffect implements BaseAudio {
  /** 是否初始化 */
  public static isInit = false;
  /** 音效实例 */
  public static effectMap: EffectMap = new Map();
  /** 音效配置 */
  private config = {
    // html5: true,
    autoplay: false,
    loop: false,
    volume: 1,
  };
  /** 是否静音 */
  private isMute = false; //true静音,false取消静音

  constructor() {
    if (!AudioEffect.isInit) {
      this.initEffect();
    }
  }

  /** 初始化音效 */
  private initEffect() {
    effectConfig.forEach((item) => {
      AudioEffect.effectMap.set(item.name, new Howl({ ...this.config, ...{ src: item.src } }));
    });
  }

  /** 播放音效 */
  public play(name: EffectName): void {
    if (!name) return;
    if (this.isMute) return;
    const findEffect = AudioEffect.effectMap.get(name);
    if (findEffect) {
      findEffect.play();
    }
  }

  /** 暂停 */
  public pause(): void {
    AudioEffect.effectMap.forEach((effect) => {
      effect.pause();
    });
  }

  /** 继续播放 */
  public continuePlay(): void {}

  /** 静音 */
  public mute(status: boolean): void {
    AudioEffect.effectMap.forEach((effect) => {
      effect.mute(status);
      this.isMute = status;
    });
  }
}

export default AudioEffect;

音效和bgm类各有不同的处理,自行看代码吧!

所有的内容写好后,我们需要一个单例管理器来统一管理:

  1. bgm的播放在初始化后就需要播放
  2. 音效需要每次被调用方调用

index.ts

import AudioBgm from "./core/audioBgm";
import AudioEffect from "./core/audioEffect";
import { EffectName } from "./config";

class AudioManager {
  private static audioBgm: AudioBgm;
  private static audioEffect: AudioEffect;
  private static initState = false;

  constructor() {
    if (!AudioManager.initState) {
      AudioManager.audioBgm = new AudioBgm();
      AudioManager.audioEffect = new AudioEffect();
      AudioManager.initState = true;
    }
  }

  /** 播放音效 */
  public playEffect(name: EffectName): void {
    AudioManager.audioEffect.play(name);
  }

  /** 静音 true静音,false取消静音 */
  public mute(status: boolean): void {
    AudioManager.audioBgm.mute(status);
    AudioManager.audioEffect.mute(status);
  }
}

export { AudioManager, EffectName };
export default AudioManager;

调用:

首次初始化我们可以放在vue的第一个vue组件,也就是App.vue中

<script lang="ts">
import { defineComponent, onMounted} from "vue";
import AudioManager from "@/utils/audio";

export default defineComponent({
  setup() {
    onMounted(() => {
      /** 音乐 */
      const audioManager = new AudioManager();
    });
  }
})

当我们需要触发音效的时候:

<script lang="ts">
import { defineComponent, onMounted} from "vue";
import { AudioManager, EffectName } from "@/utils/audio";

export default defineComponent({
  setup() {
    const audioManager = new AudioManager();

    /** 点击事件 */
    function onClick()  {
      audioManager.playEffect(EffectName.button);
    }

    return {
      onClick
    }
  }
})

由于是单例模式,所以不用操心实例化的对象不是同一个,以及资源的浪费。

版权申明

本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。

关于作者

站点职位 博主
获得点赞 0
文章被阅读 66

相关文章

目录树