You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

原生JavaScript实现音频列表多次循环播放的问题排查与修复

音频列表重复播放重叠问题的排查与修复

问题描述

我在网页中设置了多个audio元素,希望通过原生JavaScript实现如下播放逻辑:点击按钮后,每个音频文件按指定播放速度(如1.25倍)、重复次数(如额外播放2次)、播放间隔(如1秒)依次播放,之后整个列表按相同规则重复指定次数(如2次)。编写代码后,前期逻辑正常,但在列表重复阶段出现异常:第一个音频正常播放后,后续音频会同时启动播放,导致多音频重叠播放。

原始HTML代码

<button id="lsm-play-list" type="button">Play list</button>

<audio class="audio-element">
    <source src="/wp-content/uploads/2023/10/wei-geet-et-iech.mp3" type="audio/mpeg">Your browser does not support the audio element.
</audio>

<!-- other similar sections between -->

<audio class="audio-element">
    <source src="/wp-content/uploads/2023/10/wei-geet-et-iech.mp3" type="audio/mpeg">Your browser does not support the audio element.
</audio>

原始JavaScript代码

document.addEventListener('DOMContentLoaded', function () {
    const playButton = document.getElementById('lsm-play-list');
    const audioElements = document.querySelectorAll('.audio-element');

    let playbackSpeed = 1.5;  // default audio playback speed
    let pauseDuration = 1;     // default pause between plays in seconds
    let fileRepeatCount = 2;   // default audio file repeat count
    let listRepeatCount = 2;   // default playlist repeat count
    let currentAudioIndex = 0; // track current audio element
    let repeatCount = fileRepeatCount; // keep track of audio file repeats

    let isPlaylistPlaying = false;

    // event listener for 'Play list' button
    playButton.addEventListener('click', function () {
        if (!isPlaylistPlaying) {
            isPlaylistPlaying = true;
            playButton.textContent = 'Pause list';

            if (currentAudioIndex < audioElements.length - 1) {
                playAudio(currentAudioIndex);
            }
        } else {
            isPlaylistPlaying = false;
            pauseAudio(currentAudioIndex);
            playButton.textContent = 'Continue';
        }
    });

    // play audio elements
    function playAudio(index) {
        const audio = audioElements[index];

        const playCurrentAudio = () => {
            audio.currentTime = 0;
            audio.playbackRate = playbackSpeed;
            audio.play();
        };

        if (isPlaylistPlaying) {
            if (index < audioElements.length - 1) {
                currentAudioIndex = index;
            }

            playCurrentAudio();
        }

        audio.addEventListener('ended', function () {
            if (isPlaylistPlaying && repeatCount > 1) {
                repeatCount--;

                setTimeout(() => {
                    playCurrentAudio();
                }, pauseDuration * 1000); // pauses in milliseconds

            } else 

            if (isPlaylistPlaying && index < audioElements.length - 1) {
                currentAudioIndex = index + 1;
                repeatCount = fileRepeatCount;
                setTimeout(() => {
                    playAudio(index + 1);
                }, pauseDuration * 1000);
            } else 

            // here starts the playlist problematic replay part
            if (isPlaylistPlaying && currentAudioIndex === audioElements.length - 1) {
                if (listRepeatCount > 1) {
                    listRepeatCount--;
                    currentAudioIndex = 0;
                    repeatCount = fileRepeatCount;

                    // Play the playlist again
                    playAudio(0);
                } else {
                    isPlaylistPlaying = false;
                    playButton.textContent = 'Play list';
                }
            }

        });
    }

    function pauseAudio(index) {
        const audio = audioElements[index];
        audio.pause();
    }
});

问题原因

  1. 重复绑定ended事件监听器:每次调用playAudio函数时,都会给当前audio元素新增一个匿名的ended事件回调。当列表重复播放时,之前的音频元素已经累积了多个ended监听器,触发时会同时执行多个逻辑,导致后续音频被多次调用,出现重叠播放。
  2. currentAudioIndex赋值不完整:仅在非最后一个音频时更新currentAudioIndex,最后一个音频的索引没有被正确赋值,导致列表重复时的判断currentAudioIndex === audioElements.length - 1可能出现偏差。
  3. 列表重复无间隔:列表完成一轮播放后立即开始下一轮,没有遵循设定的pauseDuration间隔规则。

修复方案

1. 使用命名函数并移除旧监听器

将ended事件的回调改为命名函数,每次调用playAudio前先移除该元素上的旧监听器,避免重复绑定。

2. 完善currentAudioIndex赋值逻辑

无论当前音频是否为最后一个,都要更新currentAudioIndex为当前索引,确保判断准确。

3. 列表重复添加间隔时间

在列表重复时,添加setTimeout等待指定间隔后再开始播放下一轮。

修复后的完整JavaScript代码

document.addEventListener('DOMContentLoaded', function () {
    const playButton = document.getElementById('lsm-play-list');
    const audioElements = document.querySelectorAll('.audio-element');

    let playbackSpeed = 1.5;  // default audio playback speed
    let pauseDuration = 1;     // default pause between plays in seconds
    let fileRepeatCount = 2;   // default audio file repeat count
    let listRepeatCount = 2;   // default playlist repeat count
    let currentAudioIndex = 0; // track current audio element
    let repeatCount = fileRepeatCount; // keep track of audio file repeats

    let isPlaylistPlaying = false;
    // 存储当前音频的ended事件回调,方便移除
    let currentEndedCallback = null;

    // event listener for 'Play list' button
    playButton.addEventListener('click', function () {
        if (!isPlaylistPlaying) {
            isPlaylistPlaying = true;
            playButton.textContent = 'Pause list';
            // 修复:无论索引是否小于最后一个,都执行播放
            playAudio(currentAudioIndex);
        } else {
            isPlaylistPlaying = false;
            pauseAudio(currentAudioIndex);
            playButton.textContent = 'Continue';
        }
    });

    // play audio elements
    function playAudio(index) {
        const audio = audioElements[index];

        // 修复:先移除之前的ended监听器
        if (currentEndedCallback) {
            audio.removeEventListener('ended', currentEndedCallback);
        }

        const playCurrentAudio = () => {
            audio.currentTime = 0;
            audio.playbackRate = playbackSpeed;
            audio.play();
        };

        if (isPlaylistPlaying) {
            // 修复:无论是否为最后一个音频,都更新currentAudioIndex
            currentAudioIndex = index;
            playCurrentAudio();
        }

        // 修复:使用命名函数作为回调
        currentEndedCallback = function () {
            if (!isPlaylistPlaying) return;

            if (repeatCount > 1) {
                repeatCount--;
                setTimeout(() => {
                    playCurrentAudio();
                }, pauseDuration * 1000);
            } else if (index < audioElements.length - 1) {
                currentAudioIndex = index + 1;
                repeatCount = fileRepeatCount;
                setTimeout(() => {
                    playAudio(index + 1);
                }, pauseDuration * 1000);
            } else {
                if (listRepeatCount > 1) {
                    listRepeatCount--;
                    currentAudioIndex = 0;
                    repeatCount = fileRepeatCount;
                    // 修复:列表重复添加间隔
                    setTimeout(() => {
                        playAudio(0);
                    }, pauseDuration * 1000);
                } else {
                    isPlaylistPlaying = false;
                    playButton.textContent = 'Play list';
                }
            }
        };

        audio.addEventListener('ended', currentEndedCallback);
    }

    function pauseAudio(index) {
        const audio = audioElements[index];
        audio.pause();
    }
});

关键修复点说明

  • 移除重复监听器:通过currentEndedCallback变量存储当前回调函数,每次播放前先移除旧的监听器,避免同一元素绑定多个ended事件。
  • 完善索引更新:在播放时直接将currentAudioIndex设为当前索引,确保所有音频的索引都被正确跟踪。
  • 添加列表重复间隔:列表完成一轮后,通过setTimeout等待设定的间隔时间再开始下一轮,符合播放逻辑要求。

内容的提问来源于stack exchange,提问作者Iurie

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.07 14:58:11