【从零到一】用HTML5+CSS+JavaScript实现一个属于自己的mp3免费音乐播放器 (5) JS交互功能(曲目切换、快进快退)

简介: 手把手教你用HTML5撸个免费播放器

我们接着把剩下的音乐播放器功能也都加上~

上下曲目切换 🦀
切换上一曲 🏄🏼‍♂️

这个逻辑也很简单,也就是当你点击上一曲按钮的时候,切换到前一首歌~

功能上看上去简单,但是有很多人会在这里犯错~我简单的梳理一下逻辑

功能逻辑

当我们在第一首歌的时候,如果点击上一曲,应该切换到最后一首歌去!

在这里我们之前写的一个initMusic函数就派上用场了~

这个函数我是用来记录当前选中的音乐项目, 并且播放, 默认初始值为0

同时,我们也定义了一个currentIndex来保存索引值~

那么对这个索引值进行减法运算自然可以得到上一曲目的索引

代码如下

//切换上一曲
prevTrack.addEventListener('click',() =>{
   
    currentIndex = currentIndex - 1;
    if(currentIndex < 0){
   
        currentIndex = musicList.length - 1;
    }
    //传递播放曲目索引
    initMusic(currentIndex);

    //设置一下暂停按钮的显示状态
    playPauseBtn.textContent = '❚❚';
    //开始播放
    audio.play();
});

代码分析

利用当前曲目索引-1, 同时判断是否到顶, 也就是说当前曲目已经是第一首歌了,如果还要点击上一曲目,应该切换到最后一首歌去!

然后把计算好的索引值传递给initMusic函数

注意

这里要特别留意几个问题!~我们在看看之前的这段initMusic函数代码

function initMusic(index) {
   
    audio.src = musicList[index].src;
    musicName.textContent = musicList[index].name;
    audio.load();
    if (!audio.paused) {
   
        audio.play();
    }
}

首先我们传递过去的曲目索引值,帮助我们切换到对应的曲目,获取歌曲的一些信息~这个没什么问题

然后audio.load() 会主动触发音频元素的重新加载流程,让浏览器放弃之前的音频缓存,并且重新请求并解析新设置的src对应的音频文件,确保音频能正确关联到最新的歌曲资源~

如果你在这里不调用audio.load()时,浏览器可能不会立即响应src的变更,甚至会保留上一个音频的播放状态、时长等信息,导致新歌曲无法正常播放~比如切换歌曲后还是播放原来的音乐

所以加上audio.load()会重置音频元素的所有状态,包括当前播放时间、缓冲数据等,为新音频的播放做好准备,保证歌曲切换的流畅性和准确性~ 很多人会忽视这一点,导致不少的BUG问题~特别注意一下

同时判断if (!audio.paused) 如果没有暂停播放的情况下,就直接播放audio.play()

这里的意思其实也很简单,作用就是在重新加载新音频后,无缝延续之前的播放状态~ 也就是说原本处于播放中的音频,在我们切换曲目后自动播放新歌曲,不用我们用户再次点击播放~

可能有些人要问我那为什么要在切换上一曲的最后也加上一个audio.play()

你其实可以试试看,不加会怎么样,

很显然,你不加,默认情况下,歌曲切换是没问题的,但是就是不会播放

因为initMusic的最后逻辑里audio.play()有前置条件!audio.paused,只有音频原本处于非暂停状态也就是正在播放时才会执行播放~

如果我们不在切换上一曲目的最后加上这个audio.play()那么就没有代码能触发首次播放~懂这个意思吧

这时候,有大聪明要出来说用鼠标把进度条拖拽到中间,播放了, 这总证明音频处于非暂停状态了吧, 按上一曲目 ,同时最后不加audio.play() 也不行啊~

这是因为initMusic函数切换曲目时重新设置了audio.src并执行了audio.load()对吧~

但是这会重置音频状态为暂停paused: true

你可以用属性测试一下就知道了

function initMusic(index) {
   
    audio.src = musicList[index].src;
    musicName.textContent = musicList[index].name;
    audio.load();

    //检测音频/视频是否已暂停
    console.log(audio.paused);

    if (!audio.paused) {
   
        audio.play();
    }
}

如图

看到了吧,之前拖拽播放的非暂停状态会被清除,导致!audio.paused条件不满足,内部audio.play()不执行

知识点

function initMusic(index) {
   
    audio.src = musicList[index].src;
    musicName.textContent = musicList[index].name;
    audio.load();
    //检测音频/视频是否已暂停
    console.log(audio.paused);
    if (!audio.paused) {
   
        audio.play();
    }
}

//切换上一曲
prevTrack.addEventListener('click',() =>{
   
    currentIndex = currentIndex - 1;
    if(currentIndex < 0){
   
        currentIndex = musicList.length - 1;
    }
    initMusic(currentIndex);
    playPauseBtn.textContent = '❚❚';
    //开始播放
    audio.play();
});

另外我们在prevTrack中设置了audio.play()但是在initMusic函数中打印audio.paused还是会返回true

这是因为外部的audio.play()会异步触发音频播放,而initMusic里的console.log(audio.paused)是同步立即执行的,此时音频还未完成播放状态切换,仍显示true,但外部异步的audio.play()最终会成功启动播放。

很多人会在这里出问题就是因为这个原因~所以别当大聪明了,一定要仔细看梳理清楚逻辑才是重点!

如果你之前没有搞懂那个initMusic函数,那么现在你应该清楚了吧~

最后优化一下代码简写

prevTrack.addEventListener('click', () => {
   
    currentIndex = (currentIndex - 1 + musicList.length) % musicList.length;
    initMusic(currentIndex);
    playPauseBtn.textContent = '❚❚';
    audio.play();
});
切换下一曲 🏄🏼

上一曲目切换如果没问题,那下一曲目切换也是同理~

逻辑也很简单,也就是操作当前曲目索引值currentIndex + 1

代码如下

//切换下一曲
nextTrack.addEventListener('click',()=>{
   
    //当前曲目索引+1
    currentIndex = currentIndex + 1;
    //当超过最后一个曲目的时候,切换到第一首
    if(currentIndex>musicList.length-1){
   
        currentIndex = 0;
    }
    //传递播放曲目索引
    initMusic(currentIndex);
    //设置一下暂停按钮的显示状态
    playPauseBtn.textContent = '❚❚';
    //开始播放
    audio.play();
});
快退/快进5秒 🦗

这个功能其实本质上是修改audiocurrentTime属性

快退5秒 🌙

本质上是在currentTime的基础上减去5秒 , 同时可以结合使用Math.max函数来防止音频的当前播放时间currentTime变成负数,Math.max(0, ...) 能保证即使计算后结果小于 0,最终也只会取0

代码实现

backBtn.addEventListener('click', () => {
   
    audio.currentTime = Math.max(0, audio.currentTime - 5);
});
快进5秒 ⭐️

本质上是在currentTime的基础上加上5秒, 同时可以结合使用Math.min函数来防止音频的当前播放时间currentTime超过音频总时长duration

那么Math.min函数 能保证即使计算后结果大于总时长,最终也只会取音频总时长!

代码实现

nextBtn.addEventListener('click', () => {
   
    audio.currentTime = Math.min(audio.duration, audio.currentTime + 5);
});
双倍快退快进 🐦‍🔥

这个功能和快进快退的道理是一样的,只是设定的秒数不一样,倍数更大~

普通快进快退为5秒,那么这里双倍自然就是10秒,这根据大家自己的需求自定义即可~

这里我们就直接简单的实现一下

代码实现

// 双倍快退(10秒)
doubleBackBtn.addEventListener('click', () => {
   
    audio.currentTime = Math.max(0, audio.currentTime - 10);
});

// 双倍快进(10秒)
doubleNextBtn.addEventListener('click', () => {
   
    audio.currentTime = Math.min(audio.duration, audio.currentTime + 10);
});

基本功能的JS代码也就写得差不多了, 整体代码如下

// 歌曲列表
var musicList = [
    {
    src: 'mp3/1.wav', name: 'Once Upon A Time' },
    {
    src: 'mp3/2.wav', name: 'Instruments of Retribution' },
    {
    src: 'mp3/3.wav', name: 'Millennia' },
    {
    src: 'mp3/4.wav', name: 'We Never' }
];

//获取必要的元素
var audio = document.getElementById("audio");   //获取原始音频元素
var musicName = document.getElementById("musicName"); //获取歌曲名称容器
var playPauseBtn = document.getElementById("playPauseBtn"); //暂停和播放按钮
var stopBtn = document.getElementById('stopBtn'); //停止播放按钮
var progressBar = document.getElementById('progressBar'); //获取中层进度条, 控制显示它的状态
var resetBtn = document.getElementById('resetBtn'); //重听按钮
var progressContainer = document.getElementById('progressContainer'); //进度条
var prevTrack = document.getElementById('prevTrack'); //上一曲按钮
var nextTrack = document.getElementById('nextTrack'); //下一曲按钮
var backBtn = document.getElementById('backBtn'); //快退5秒
var nextBtn = document.getElementById('nextBtn'); //快进5秒
var doubleBackBtn = document.getElementById('doubleBackBtn'); //双倍快退
var doubleNextBtn = document.getElementById('doubleNextBtn'); //双倍快进


//定义一个currentIndex变量,用来记录当前选中/激活/播放的项目初始值为0
var currentIndex = 0;
//定义initMusic函数
function initMusic(index) {
   
    audio.src = musicList[index].src;
    musicName.textContent = musicList[index].name;
    audio.load();
    if (!audio.paused) {
   
        audio.play();
    }
}
//初始化一下
initMusic(currentIndex);


// 播放/暂停
playPauseBtn.addEventListener('click', () => {
   
    if (audio.paused) {
   
        audio.play();
        playPauseBtn.textContent = '❚❚';
    } else {
   
        audio.pause();
        playPauseBtn.textContent = '▶';
    }
});

//停止播放按钮
stopBtn.addEventListener('click', () => {
   
    audio.pause();
    audio.currentTime = 0;
    playPauseBtn.textContent = '▶';
    progressBar.style.width = '0%';
});

// 重听当前歌曲
resetBtn.addEventListener('click', () => {
   
    audio.currentTime = 0;

    //这里要考虑到如果用户点击了暂停键,要重听的情况下也要播放
    if (audio.paused) {
   
        //开始播放
        audio.play();
        //设置暂停与播放按钮的内容为❚❚
        playPauseBtn.textContent = '❚❚';
    }
});

progressContainer.addEventListener('click', (e) => {
   
    var rect = progressContainer.getBoundingClientRect();
    //计算当前点击的位置
    var clickX = e.clientX - rect.left;
    audio.currentTime = (clickX / rect.width) * audio.duration;
    //同时播放音乐
    audio.play();
});

audio.addEventListener('timeupdate', () => {
   
    if (!audio.duration) return;
    var progress = (audio.currentTime / audio.duration) * 100;
    progressBar.style.width = progress + '%';
});


//上一曲
prevTrack.addEventListener('click', () => {
   
    currentIndex = currentIndex - 1;
    if (currentIndex < 0) {
   
        currentIndex = musicList.length - 1;
    }
    initMusic(currentIndex);
    playPauseBtn.textContent = '❚❚';
    audio.play();
});


//下一曲
nextTrack.addEventListener('click', () => {
   
    //当前曲目索引+1
    currentIndex = currentIndex + 1;
    //当超过最后一个曲目的时候,切换到第一首
    if (currentIndex > musicList.length - 1) {
   
        currentIndex = 0;
    }
    //传递播放曲目索引
    initMusic(currentIndex);
    //设置一下暂停按钮的显示状态
    playPauseBtn.textContent = '❚❚';
    //开始播放
    audio.play();
});

//快退5秒
backBtn.addEventListener('click', () => {
   
    audio.currentTime = Math.max(0, audio.currentTime - 5);
});

//快进5秒
nextBtn.addEventListener('click', () => {
   
    audio.currentTime = Math.min(audio.duration, audio.currentTime + 5);
});


// 双倍快退(10秒)
doubleBackBtn.addEventListener('click', () => {
   
    audio.currentTime = Math.max(0, audio.currentTime - 10);
});

// 双倍快进(10秒)
doubleNextBtn.addEventListener('click', () => {
   
    audio.currentTime = Math.min(audio.duration, audio.currentTime + 10);
});
// 解决自动播放拦截
document.addEventListener('click', () => audio.play().catch(() => {
    }),{
    once: true });

最后 🥳

到这里这个简易的web音乐播放器的大致功能我们就做到这里了,其实这个播放器我们还可以继续挖掘它的功能,更具需求不断改进,玩出更多花样~这就要看大家的时间和耐心了

以后有时间,我还会更新一些有趣的小功能~大家一定要多多关注哦!

相关文章
|
7天前
|
存储 JavaScript 前端开发
JavaScript基础
本节讲解JavaScript基础核心知识:涵盖值类型与引用类型区别、typeof检测类型及局限性、===与==差异及应用场景、内置函数与对象、原型链五规则、属性查找机制、instanceof原理,以及this指向和箭头函数中this的绑定时机。重点突出类型判断、原型继承与this机制,助力深入理解JS面向对象机制。(238字)
|
6天前
|
云安全 人工智能 安全
阿里云2026云上安全健康体检正式开启
新年启程,来为云上环境做一次“深度体检”
1622 6
|
2天前
|
消息中间件 人工智能 Kubernetes
阿里云云原生应用平台岗位急招,加入我们,打造 AI 最强基础设施
云原生应用平台作为中国最大云计算公司的基石,现全面转向 AI,打造 AI 时代最强基础设施。寻找热爱技术、具备工程极致追求的架构师、极客与算法专家,共同重构计算、定义未来。杭州、北京、深圳、上海热招中,让我们一起在云端,重构 AI 的未来。
|
3天前
|
存储 人工智能 自然语言处理
OpenSpec技术规范+实例应用
OpenSpec 是面向 AI 智能体的轻量级规范驱动开发框架,通过“提案-审查-实施-归档”工作流,解决 AI 编程中的需求偏移与不可预测性问题。它以机器可读的规范为“单一真相源”,将模糊提示转化为可落地的工程实践,助力开发者高效构建稳定、可审计的生产级系统,实现从“凭感觉聊天”到“按规范开发”的跃迁。
590 11
|
8天前
|
安全 数据可视化 网络安全
安全无小事|阿里云先知众测,为企业筑牢防线
专为企业打造的漏洞信息收集平台
1335 2
|
7天前
|
缓存 算法 关系型数据库
深入浅出分布式 ID 生成方案:从原理到业界主流实现
本文深入探讨分布式ID的生成原理与主流解决方案,解析百度UidGenerator、滴滴TinyID及美团Leaf的核心设计,涵盖Snowflake算法、号段模式与双Buffer优化,助你掌握高并发下全局唯一ID的实现精髓。
368 160
|
7天前
|
人工智能 自然语言处理 API
n8n:流程自动化、智能化利器
流程自动化助你在重复的业务流程中节省时间,可通过自然语言直接创建工作流啦。
455 6
n8n:流程自动化、智能化利器
|
9天前
|
人工智能 API 开发工具
Skills比MCP更重要?更省钱的多!Python大佬这观点老金测了一周终于懂了
加我进AI学习群,公众号右下角“联系方式”。文末有老金开源知识库·全免费。本文详解Claude Skills为何比MCP更轻量高效:极简配置、按需加载、省90% token,适合多数场景。MCP仍适用于复杂集成,但日常任务首选Skills。推荐先用SKILL.md解决,再考虑协议。附实测对比与配置建议,助你提升效率,节省精力。关注老金,一起玩转AI工具。
|
16天前
|
机器学习/深度学习 安全 API
MAI-UI 开源:通用 GUI 智能体基座登顶 SOTA!
MAI-UI是通义实验室推出的全尺寸GUI智能体基座模型,原生集成用户交互、MCP工具调用与端云协同能力。支持跨App操作、模糊语义理解与主动提问澄清,通过大规模在线强化学习实现复杂任务自动化,在出行、办公等高频场景中表现卓越,已登顶ScreenSpot-Pro、MobileWorld等多项SOTA评测。
1634 7
|
5天前
|
IDE 开发工具 C语言
【2026最新】VS2026下载安装使用保姆级教程(附安装包+图文步骤)
Visual Studio 2026是微软推出的最新Windows专属IDE,启动更快、内存占用更低,支持C++、Python等开发。推荐免费的Community版,安装简便,适合初学者与个人开发者使用。
600 10