HTML5JS在同一时间多次播放相同的声音

HTML5 JS play same sound multiple times at the same time

本文关键字:声音 播放 同一时间 HTML5JS      更新时间:2023-09-26

我正在创建一个非常基本的HTML5游戏,我遇到了一堵小墙,基本上我有一个数组,这个数组由一个声音文件填充(这是一个数组所以如果需要,我可以分配更多的声音)

this.Sounds["L_Explosion1"] = new Audio("Assets/Sounds/Explosion_1.wav");
this.Sounds["L_Explosion1"].volume = 1;
this.Sounds["L_Explosion1"].load();

当玩家按下空格键时,我想播放声音,所以我运行这个代码

this.Sounds["L_Explosion1"].play();

然而,如果我在播放声音之前再次按下空格键,它将不会播放,我试图做的是多次播放相同的声音,如果相同的声音尚未完成,我将如何实现这一点?

为了同时播放多个声音实例,您需要创建该Audio元素的新副本。一种方法是使用.cloneNode()方法,每次按下空格键时播放克隆的音频。

this.Sounds["L_Explosion1"].cloneNode(true).play();

HTML5音频元素有很多局限性,它不太适合像您描述的那样复杂的音频用例,这似乎是一个游戏。

相反,请考虑使用WebAudio API。你必须:

  • 使用XMLHttpRequest将音频文件加载到缓冲区中

  • 将该缓冲区分配给AudioBufferSourceNode实例。

  • 将该节点连接到适当的目的地或中间节点(例如GainNode)。

  • 如果您还需要停止声音,则需要跟踪已创建但尚未完成播放的每个源节点,这可以通过收听源节点的onended来完成。

这里有一个类,它封装了从URL加载声音并根据需要多次播放/停止声音的逻辑,跟踪所有当前播放的源并根据需要进行清理:

window.AudioContext = window.AudioContext || window.webkitAudioContext;
const context = new AudioContext();
export class Sound {
    url = '';
    buffer = null;
    sources = [];
    constructor(url) {
        this.url = url;
    }
    load() {
        if (!this.url) return Promise.reject(new Error('Missing or invalid URL: ', this.url));
        if (this.buffer) return Promise.resolve(this.buffer);
        return new Promise((resolve, reject) => {
            const request = new XMLHttpRequest();
            request.open('GET', this.url, true);
            request.responseType = 'arraybuffer';
            // Decode asynchronously:
            request.onload = () => {
                context.decodeAudioData(request.response, (buffer) => {
                    if (!buffer) {
                        console.log(`Sound decoding error: ${ this.url }`);
                        reject(new Error(`Sound decoding error: ${ this.url }`));
                        return;
                    }
                    this.buffer = buffer;
                    resolve(buffer);
                });
            };
            request.onerror = (err) => {
                console.log('Sound XMLHttpRequest error:', err);
                reject(err);
            };
            request.send();
        });
    }
    play(volume = 1, time = 0) {
        if (!this.buffer) return;
        // Create a new sound source and assign it the loaded sound's buffer:
        const source = context.createBufferSource();
        source.buffer = this.buffer;
        // Keep track of all sources created, and stop tracking them once they finish playing:
        const insertedAt = this.sources.push(source) - 1;
        source.onended = () => {
            source.stop(0);
            this.sources.splice(insertedAt, 1);
        };
        // Create a gain node with the desired volume:
        const gainNode = context.createGain();
        gainNode.gain.value = volume;
        // Connect nodes:
        source.connect(gainNode).connect(context.destination);
        // Start playing at the desired time:
        source.start(time);
    }
    stop() {
        // Stop any sources still playing:
        this.sources.forEach((source) => {
            source.stop(0);
        });
        this.sources = [];
    }
}

然后你可以做这样的事情:

const soundOne = new Sound('./sounds/sound-one.mp3')
const soundTwo = new Sound('./sounds/sound-two.mp3')
Promises.all([
  soundOne.load(),
  soundTwo.load(),
]).then(() => {
  buttonOne.onclick = () => soundOne.play();
  buttonTwo.onclick = () => soundOne.play();
})