原生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(); } });
问题原因
- 重复绑定
ended事件监听器:每次调用playAudio函数时,都会给当前audio元素新增一个匿名的ended事件回调。当列表重复播放时,之前的音频元素已经累积了多个ended监听器,触发时会同时执行多个逻辑,导致后续音频被多次调用,出现重叠播放。 currentAudioIndex赋值不完整:仅在非最后一个音频时更新currentAudioIndex,最后一个音频的索引没有被正确赋值,导致列表重复时的判断currentAudioIndex === audioElements.length - 1可能出现偏差。- 列表重复无间隔:列表完成一轮播放后立即开始下一轮,没有遵循设定的
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
相关产品推荐
相关产品推荐

