HTML5音频 - 点击后无论前一次循环是否完成,都会重复播放声音。

24

我正在制作一个游戏,在游戏中点击时会播放一个wav文件,这个文件是枪声“bang”。

问题是,如果我快速点击,它不会为每次点击播放声音 - 就好像在播放声音时忽略了点击,一旦声音播放完毕,它就会再次开始监听点击。延迟似乎大约为一秒钟,所以你可以想象如果有人每秒点击4或5次,我希望听到5声“bang”,而不是1声。

这是我的HTML代码:

<audio id="gun_sound" preload>
    <source src="http://www.seancannon.com/_test/audio/gun_bang.wav" />
</audio>

这是我的JS代码:
$('#' + CANVAS_ID).bind(_click, function() {
    document.getElementById('gun_sound').play();
    adj_game_data(GAME_DATA_AMMO_ID, -1);
    check_ammo();
}); 

有什么想法吗?
4个回答

20

我知道这是一个非常晚的回答,但我只是想...不过在寻找解决方案时,我发现在我的情况下最好的方法是预加载声音,然后每次想播放它时克隆节点,这使我可以即使上一个声音未结束也能播放:

var sound = new Audio("click.mp3");
sound.preload = 'auto';
sound.load();

function playSound(volume) {
  var click=sound.cloneNode();
  click.volume=volume;
  click.play();
}

这很棒。有没有办法使用多个mp3来完成同样的事情? - gravityboy
这很好,但是每次新的点击都会增加一个HTTP请求 :( - NiCk Newman
在Chrome中运行得非常好,但出于某种原因Firefox无法播放。我可以在Firefox选项卡上看到扬声器图标,所以有音频,如果我从我的PC上播放任何其他音频(YouTube视频,Skype通话等),那么Firefox甚至会发出声音 - 否则我听不到它 :-/ 有什么想法吗?我正在通过for循环发射许多克隆,所以可以将其视为机枪射击。 - Beezle-Bug
嗨@jmservera,我在想这种克隆方法会不会导致内存泄漏?因为克隆的元素从未被清理,对吧? - mupinnn
嗨@mupinnn。正如您所看到的,这是一个古老的解决方法(10年前的),不应在更新的浏览器中使用;相反,请使用现代API或类似https://howlerjs.com/的东西。 然而,回答您的问题:变量是局部函数,并且元素未附加到文档中。此外,它会被每个克隆替换,因此理论上垃圾收集器应该清除任何未使用的克隆。 - jmservera
@jmservera 有没有更少古老的解决方法,而不涉及使用外部库? - Trever Thompson

16

一旦预加载了 gun_bang.wav,您可以动态创建带有该声音的新元素,播放音频,然后在音频完成时将它们删除。

function gun_bang() {
  var audio = document.createElement("audio");
  audio.src = "http://www.seancannon.com/_test/audio/gun_bang.wav";
  audio.addEventListener("ended", function() {
    document.removeChild(this);
  }, false);
  audio.play();
}

$('#' + CANVAS_ID).bind(_click, function() {
  gun_bang();
  adj_game_data(GAME_DATA_AMMO_ID, -1);
  check_ammo();
});

2
这有点过头了。只需将currentTime设置为0即可:https://dev59.com/QWw05IYBdhLWcg3w0VKa#7005562 - katspaugh
2
@AllenWebguy,你可以使用jQuery完成,代码如下:$('selector')[0].play(); - Volomike
2
@katspaugh 那样会切断声音。 - Maverick
嘿,@Maverick谢谢!只有一个问题,我们应该同时移除EventListener吗?还是说这样就可以了?(内存泄漏?) - NiCk Newman
实际上,这种方法每次都会创建一个新的请求,这并不好。有没有一种方法可以检查它是否已经加载,并在点击时重复播放?(而不是每次点击都进行新的请求?) - NiCk Newman
显示剩余2条评论

1

您可以使用自己的程序特定点击逻辑来包装以下内容,但以下内容演示了同时播放3个枪声。如果您需要任何类型的延迟,可以引入一个小的sleep()函数(许多临时解决方案都很容易找到),或者您可以使用setTimeout()调用后续函数。

<audio id="gun_sound" preload="preload">
    <source src="http://www.seancannon.com/_test/audio/gun_bang.wav" />
</audio>

<script type="text/javascript">
jQuery(document).ready(function($){

  $('#gun_sound').clone()[0].play();
  $('#gun_sound').clone()[0].play();
  $('#gun_sound').clone()[0].play();

});
</script>

preload="preload" 在 HTML5 中是否会在点击播放按钮之前加载音频? - Mona Jalal

1

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();
})

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接