【从零到一】用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音乐播放器的大致功能我们就做到这里了,其实这个播放器我们还可以继续挖掘它的功能,更具需求不断改进,玩出更多花样~这就要看大家的时间和耐心了

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

相关文章
|
2月前
|
Java 开发工具 Android开发
Flutter环境搭建-Window环境
Flutter环境搭建以及搭建的常见问题
305 6
|
2月前
|
移动开发 前端开发 JavaScript
|
2月前
|
Web App开发 安全
Chrome浏览器自动拦截某些下载内容 解决方案
Chrome下载文件常因安全机制被拦截。可通过设置允许不安全内容解决:在地址栏输入`chrome://settings/content/insecureContent`,添加`[*.]com`等域名,即可免手动确认下载。建议按需添加,兼顾安全与便利。(239字)
865 3
Chrome浏览器自动拦截某些下载内容 解决方案
|
2月前
|
人工智能 安全 API
Nacos 安全护栏:MCP、Agent、配置全维防护,重塑 AI Registry 安全边界
Nacos安全新标杆:精细鉴权、无感灰度、全量审计!
1131 76
|
2月前
|
运维 Kubernetes Go
别再靠人肉运维了:Kubernetes Operator 才是运维自动化的终极形态
别再靠人肉运维了:Kubernetes Operator 才是运维自动化的终极形态
108 6
|
2月前
|
人工智能 自然语言处理 API
数据合成篇|多轮ToolUse数据合成打造更可靠的AI导购助手
本文提出一种面向租赁导购场景的工具调用(Tool Use)训练数据合成方案,以支付宝芝麻租赁助理“小不懂”为例,通过“导演-演员”式多智能体框架生成拟真多轮对话。结合话题路径引导与动态角色交互,实现高质量、可扩展的合成数据生产,并构建“数据飞轮”推动模型持续优化。实验表明,该方法显著提升模型在复杂任务中的工具调用准确率与多轮理解能力。
395 43
数据合成篇|多轮ToolUse数据合成打造更可靠的AI导购助手
|
3月前
|
监控 安全 Unix
iOS 崩溃排查不再靠猜!这份分层捕获指南请收好
从 Mach 内核异常到 NSException,从堆栈遍历到僵尸对象检测,阿里云 RUM iOS SDK 基于 KSCrash 构建了一套完整、异步安全、生产可用的崩溃捕获体系,让每一个线上崩溃都能被精准定位。
756 84
|
2月前
|
设计模式 XML NoSQL
从HITL(Human In The Loop) 实践出发看Agent与设计模式的对跖点
本文探讨在ReactAgent中引入HITL(人机回路)机制的实践方案,分析传统多轮对话的局限性,提出通过交互设计、对话挂起与工具化实现真正的人机协同,并揭示Agent演进背后与工程设计模式(如钩子、适配器、工厂模式等)的深层关联,展望未来Agent的进化方向。
698 45
从HITL(Human In The Loop) 实践出发看Agent与设计模式的对跖点

热门文章

最新文章