HTML+js 简易的音乐播放器(包含原理解释)

  • 综述
  • HTML audio 简介及开发环境
  • 一、audio控件
  • 1. audio控件的基本属性
  • 2.audio控件的基本响应事件
  • 二、开发环境
  • 代码及原理介绍
  • 前言
  • 1.添加播放器控件及对应的资源
  • 2. 播放列表的制作
  • 3. 播放列表的动态加载
  • 4.功能
  • (1) 点击播放列表切换乐曲,并改变正在播放的乐曲那一栏的样式
  • (2)上一首、下一首和顺序播放
  • (3)单曲循环
  • (4) 随机播放
  • (5) 播放列表的滚动
  • 结语


综述

为了符合工作需求和学习目标,笔者最近正在学习HTML多媒体相关的内容,其中的一个重点便是音乐播放器,但纵观大部分网站提供的源码及教程,都并未出现相对完整的HTML音乐播放器教学,结合我这几天的学习和实践,总结了一套相对简单的HTML音乐播放器集成代码,在此向大家演示并解释该播放器的内容和愿意,希望能帮助到更多愿意学习web编程的人及业内人员。

HTML audio 简介及开发环境

一、audio控件

直到现在,仍然不存在一项旨在网页上播放音频的标准。今天,大多数音频是通过插件(比如现金已经被淘汰的Flash)来播放的。然而,并非所有浏览器都拥有同样的插件。HTML5 规定了一种通过 audio 元素来包含音频的标准方法,该元素能够播放声音文件或者音频流1。其标准写法如下:

<audio></audio>

1. audio控件的基本属性

  • autoplay:值:autoplay,如果出现该属性,则音频在就绪后马上播放(现在似乎无效)。
  • controls:值:controls,如果出现该属性,则向用户显示控件,比如播放按钮。
  • loop:值:loop如果出现该属性,则每当音频结束时重新开始播放。
  • preload:值:preload,如果出现该属性,则音频在页面加载时进行加载,并预备播放(如果使用
    “autoplay”,则忽略该属性
    )。 src:值:“url”,要播放的音频的 URL。
  • currentTime:包含当前播放时间,以秒为单位。设置此属性会将播放时间设置为定义的值。
  • duration:(只读)长度,以秒为单位。
  • paused:(只读):指示播放是否暂停。 volume:它获取或设置媒体元素的音量:0.0是静音,1.0是最响亮的。

2.audio控件的基本响应事件

  • progress:用户代理正在获取媒体数据。
  • error:获取媒体数据时发生错误。
  • play:播放已经开始。在play()方法返回时,或当autoplay属性导致播放开始时激发。
  • pause:播放已暂停。pause()方法返回后触发。
  • loadeddata:用户代理可以首次在当前播放位置呈现媒体数据。
  • waiting:播放已停止,因为下一帧不可用,但用户代理希望该帧很快就可用。
  • playing:播放已开始。
  • canplay:用户代理可以恢复媒体数据的回放,但是估计如果现在要开始回放,则媒体资源不能以当前回放速率呈现直到其结束,而不必停止进一步缓冲内容。
  • seeking:搜索IDL属性已更改为true并且seek操作花费的时间足以使用户代理有时间触发事件。
  • seeked:寻求IDL属性更改为false。
  • timeupdate:当前播放位置作为正常播放的一部分或以特别有趣的方式改变,例如不连续。Note:此事件将每秒触发一次。
  • ended:播放已停止,因为已达到媒体资源的结尾(当audio属性中含有循环播放属性“loop”时,此方法将失效)。
  • volumechange:volume属性或muted属性已更改。在相关属性的setter返回后触发。

二、开发环境

  • 操作系统:windows 11
  • 环境:JDK 1.8.0_331
  • 开发软件:Netbeans 12.3

代码及原理介绍

前言

本文中提及的音乐播放器为单独页面,不含css和js文件,全部功能仅有一个HTML页面实现,没有多余的东西,下文的代码是逐渐完善的代码片,不是一个整体。直接从本文复制并使用笔者的代码的时候请看清楚,不要一股脑地随意复制粘贴。(只当一个CV程序员你就等着喝西北风吧,哦,国企程序员啊,当我没说……)

1.添加播放器控件及对应的资源

新建一个html页面并在body内添加audio控件,暂时不要写其自带的属性:

...
<div>
	<audio></audio>
</div>
...

通常,在谷歌浏览器下,audio控件看起来是这样的:

html5 嵌入播放器 html简单播放器_javascript


资源可以随意添加,比如,我们现在就有三首音乐在我的项目根目录 src/audios 下

html5 嵌入播放器 html简单播放器_javascript_02

2. 播放列表的制作

制作一个单行的表格,这个表格作为播放列表应该是可以滚动显示的,因此要把表格装载到一个高度固定的div中。首先是添加一个表格:

<h2>播放列表</h2>
	<div class="musicList">
    	<table id="myTable"></table>
    </div>
    <button>上一首</button>
    <button>下一首</button>
    <button id="music_rand">随机播放</button>
    <input type="checkbox" id="loop_ck">单曲循环</button>

为表格添加亿一点点样式,关于个样式代表的含义,这里不做深入解释了,懂点CSS3的人就能看得懂,以及不要计较里面部分很奇怪的样式,不加他们你会后悔的,自己用浏览器调试工具看看效果,这段代买具体可以写在head标签下的style标签中:

<style>
	.musicList{width: 300px; height: 10em;border: 1px solid black;table-layout: fixed;}
	#myTable{width: 100%;border: 1px solid black;border-collapse: collapse;table-layout: fixed;overflow-y: auto;}
	/* 为什么把myTable的高度超出样式设置为自动,后文会讲到 */
	td{text-align: left;font-weight: 700; border: 1px solid black;}
	th{text-align: left;writing-mode: vertical-rl;transform: rotate(180deg);padding: 0.25em;vertical-align: text-bottom;border: 1px solid black}
</style>

表格呈现出的具体样式就是这样:

html5 嵌入播放器 html简单播放器_前端_03

3. 播放列表的动态加载

不用废话了,就是ajax,有jquery最好(还是那句话,免费的,自己去下一个,以及不要深入学,不然等于49年入*军)。你问我为什么动态加载?这位读者,你是否清醒?播放列表当然会更新啊,你最好是自己会写有关文件上传的东西(不会就搜一个,Servlet上传文件方便的很,甚至都不需要Spring等框架,我总是致力于教大家最简单的东西,所以本文不会涉及文件上传,毕竟那不是必要的东西,而且使用某个播放器的用户也大概率不会自己上传,播放器联网更新后台数据是尝试吧,诸如网易云等诸多平台都是这样做的)。
我用于存储音乐资源列表的是一个JSON文件,内含一个数组,可以通过文件上传实现更新,读者们最好自己也准备一个。

musicName.json:

["歌唱动荡的青春-Lube", "guitar", "lite"]

然后就是添加请求该json的一个ajax,具体如下,写在页面的script标签体内:

<script>
var files = [];
var audio = document.getElementById("audio1");
$.ajax({
	url: "./json/musicName.json",
	type: "get",
	success: function (res) {
		files = res;
		let ls = res;
		// 动态添加音乐资源于audio控件,这条语句↓的作用是设置默认播放的音乐,我选择的是json中的首位
		$("#audio1").prop("src", "src/audio/" + res[0] + ".mp3");  
		for (let i = 0; i < res.length; i++) {
		/* 动态生成表格内容,实际上我不推荐这样做,乐曲数量太多会给网页带来加载负担,最好是分次数动态请求,笔者这里是因为只有
		三个就稍微写简单一点,笔者会写分次请求,有需要时会教给各位读者。 */
		$("#myTable").append('<tr><td><input type="text" class="ML" readonly id="ml' + i + '" value="' + ls[i] 
		+ '" οnclick="musicPlaying(`ml' + i + '`)"></td></tr>');
		}
	}
});
</script>

这段代码很多人会有疑惑:第一,为什么会有一个全局变量获取了请求返回的结果res?后面会用到的,还要设计很多内容,为了保证这个数组不会消失,笔者选择以全局变量的形式将其保存,读者们如果会用session和cookie也可以,笔者依旧是那个宗旨,只教最简单的方式,优化代码更方便。第二,res这个返回值可以直接用,为何多写一层内部变量ls承载它?谨慎,毕竟这不是一个公开方法,只是请求成功的内置方法函数,最好用内部变量承载ajax请求成功的返回值,因为可能会涉及到对res的修改,直接修改res会造成一些不必要的效果或错误,尽量修改一个承载变量,而非返回值本身。
各位读者能看到,该请求成功函数将一个input输入框以只读模式加载进了单行表格中,这样做的目的是用户可以点击这个输入框实现音乐的切换,我已经提前内置好一个叫做musicPlaying的函数了,为此,我们需要给这个输入框规定一个类,并修改他们的样式:

<style>
	...
	.ML{width: 95.5%;padding-left: 0.5em;outline: none;cursor: pointer}
	/* 光标指针过于难看,因此用cursor定义了手指类型,也可以附加url使用你们的鼠标图示 */
</style>

当ajax请求完成之时,我们就能看到表格的变化了,此时由于乐曲资源已经动态成为了audio标签体内的属性,因此它也变得可以操作了:

html5 嵌入播放器 html简单播放器_jquery_04

4.功能

(1) 点击播放列表切换乐曲,并改变正在播放的乐曲那一栏的样式

这是音乐播放列表的基本功能之一,内置函数musicPlaying开始生效,核心原理是,用户点击了这一栏以后,系统获取到input框内的值,将这个值变成适用于audio控件src属性的完整url,加载这首音乐,然后播放,不仅仅是音乐,视频播放器也可以用我的这个逻辑,B站都上过吧(不是广告不是广告不是广告

B站UP主“阿卡大文”先生的某个合集视频的播放列表:

html5 嵌入播放器 html简单播放器_前端_05


上代码:

function musicPlaying(id) {
	audio.pause();  // 暂停
	$("#audio1").prop("src", "");  // 置空资源,强行停止播放,算是一个保险
	/* 点击后用新的value填充 */
	let val = $("#" + id).val();
	$("#audio1").prop("src", "src/audio/" + val + ".mp3");
	audio.play();  // 播放
}
function Playing() {
	let name = decodeURI($("#audio1").prop("src").replace("http://localhost:8443/Blog/src/audio/", "").replace(".mp3", ""));
	$("input[id*=ml]").each(function () {
		let val = $(this).val();
		let now = null;
//		  console.log(name);
		if (val.indexOf("正在播放") !== -1) {
			now = val.replace("正在播放 —— ", "");
			if (now === name) {
				$(this).css({"background-color": "black", "color": "white"});
				$(this).val(val);
			} else {
				$(this).css({"background-color": "white", "color": "black"});
				if (val.indexOf("正在播放 —— ") !== -1) {
					$(this).val(now);
				}
			}
		} else {
			if (val === name) {
				$(this).css({"background-color": "black", "color": "white"});
				$(this).val("正在播放 —— " + val);
			} else {
				$(this).css({"background-color": "white", "color": "black"});
			}
		}
	});
}

如何调用这个函数呢?如果直接调用,肯定有几率和ajax请求冲突,因此笔者在ajax请求完成后为页面添加了一个监听器,监听我们上文提到的play方法,关于事件监听器的方法不赘述了,读者们可以自行搜索:

$.ajax({
	// 略
	complete: function () {
		audio.addEventListener("play", Playing);
	}
});

解释一下为什么会出现now这个中间变量,因为正在播放的乐曲对应播放列表那一栏的value已经更改了,因此要去掉前缀再进行判断,不然这函数的判断条件会使得函数内容间隔执行,这不是我们想要的效果。虽然很简单,但笔者觉得这部分思想已经传达到了,下面是加入了这个部分以后的播放效果:

html5 嵌入播放器 html简单播放器_html5 嵌入播放器_06

(2)上一首、下一首和顺序播放

(该节代码存在参考2
就如同网易云播放列表的上一首和下一首一样,只是我们采取我们采取比较简单的方式。首先是顺序播放的功能,这个部分其实也不难,之前说过全局变量files的问题,现在就可以用上了,笔者的逻辑是,监听ended结束播放时间,使用一个索引变量index来记录当前乐曲在files中的位置,当播放完成后自动切换下一首(index++),如果此时已经是列表末尾了,则直接将index至于0,代码如下:

function next() {
	if (index === files.length - 1) {
		index = 0;
	} else {
		index++;
	}
	$("#audio1").prop("src", "src/audio/" + files[index] + ".mp3");
	audio1.play();
}

在ajax的complete方法中添加ended事件监听器:

$.ajax({
	// 略
	complete: function () {
		audio.addEventListener("play", Playing);
		audio.addEventListener("ended", next, false);
	}
});

为什么这个方法有一个false?这是事件监听器的第三个参数,主要的目的是区别事件冒泡(false,由内而外,直接抵达触发该事件的控件,然后在上升至整个html document)和事件捕捉(true,由外而内,由html document逐级下降,最终找到触发事件的位置,也就是audios控件)。使用false直接冒泡,时间效率会高很多,虽然可能只是快零点几毫秒,但足够了。
与此同时,这个方法同样可以用来实现手动播放下一首乐曲的功能,因此我们将其与“下一首”按钮绑定,笔者这里直接用的标签体内属性onclick你会用jquery和vue的框架也可以,我把这三个常用框架的表示模式都写出来,以防某些人懒到想直接CV甚至不用改。后面不赘述了,只讲最简单的一种:

html标签体的基本写法:

<button onclick="next();">下一首</button>

js内的多种写法(部分例子):

// 记得给button添加id,我这里不赘述,直接写一个id
$("#next").click(()=>{next();});
$("input[id=next]").click(function(){next();});
document.getElementById("next").onclick = function(){next();};

如果使用了Vue,可以这样写:
(html内)

<button @click="next();">下一首</button>
或者
<button v-on="{click:next}">下一首</button>

(js内)

// 前略
methods:{
   next(){
   	//  内容就是刚才next函数的内容
   },
}

以上就是下一首和顺序播放的功能设置与实例化绑定,接下来要进行上一曲的操作,只需要调转next函数的逻辑即可,获取当前的索引位,然后依次递减1,如果已经递减到0了那就回到尾部。直接上代码:

function pre() {
	if (index === 0) {
		index = files.length - 1;
	} else {
		index--;
	}
	$("#audio1").prop("src", "src/audio/" + files[index] + ".mp3");
	audio1.play();
}

然后给上一首button控件绑定这个方法:

<button onclick="pre();">上一首</button>

(3)单曲循环

所谓单曲循环,其实就是为audio控件动态添加loop属性,甚至不需要写成loop=“loop”,就像input的disabled和readonly一样,浏览器的客制化功能将自动补全属性值,逻辑就是每当点击checkbox时,如果其被勾选了,就添加这个属性开启循环,反之丢掉这个属性退出循环,值得注意的是,循环开启后自动切歌的功能就失效了,因为loop属性的内核其实相当于强行把进度条拖到最开始的位置,而不是重新开始放这首歌。代码如下:

function musicLoop() {
	if ($("#loop_ck").prop("checked")) {
		$("#audio1").attr("loop", true);
	} else {
		$("#audio1").removeAttr("loop");
	}
}

同时给单曲循环的checkbox绑定上这个功能:

<input type="checkbox" id="loop_ck" onclick="musicLoop();">单曲循环</button>

(4) 随机播放

逻辑是,调用生成随机数的函数,生成一个数值在0到数组长度-1(最后一位索引)范围之内的随机数,以此作为索引,获取资源名称并改变audio控件的资源值,这里为了方便(预设了控件id)我直接用jquery绑定了事件函数,代码如下:

// 生成随机数的函数 ↓
function randomNum(minNum, maxNum) {
	switch (arguments.length) {
		case 1:
			return parseInt(Math.random() * minNum + 1, 10);
			break;
		case 2:
			return parseInt(Math.random() * (maxNum - minNum + 1) + minNum, 10);
			break;
		default:
			return 0;
			break;
	}
}

$("#music_rand").click(function () {
	index = randomNum(0, files.length - 1);
	$("#audio1").prop("src", "src/audio/" + files[index] + ".mp3");
	audio1.play();
});

(5) 播放列表的滚动

先前为列表设置了高度超出自动变化的样式,在这里就能用上了首先要预设播放列表单页的最大显示量,笔者这里给容器的高度限定死了为10em,因此限定表格最多显示六行,该程序的逻辑原理是让表格超出6行以后就滚动显示。如果有需要,读者可自行释放笔者代码的注释部分,这让表格容器可以实时随着表格的高度变化自己的宽度。为了确保该代码不失效,也必须限制其在完成ajax请求后执行,如下:

function makeTableScroll() {
	var maxRows = 6;
	var table = document.getElementById('myTable');
	var wrapper = table.parentNode;
	var rowsInTable = table.rows.length;
	var height = 0;
	if (rowsInTable > maxRows) {
		for (var i = 0; i < maxRows; i++) {
			height += table.rows[i].clientHeight;
		}
//  	wrapper.style.height = height + "px";
	}
}
...
...
...
$.ajax({
	// 略
	complete: function () {
		makeTableScroll() ;
		audio.addEventListener("play", Playing);
		audio.addEventListener("ended", next, false);
	}
});

之后我们就得到可以滚动的播放列表了(不要在意内容,笔者可以添加了重复名称用以测试):

html5 嵌入播放器 html简单播放器_html5 嵌入播放器_07

关于其他的功能,诸如样式、变化等,各位可以自行再添加,本文不做其他赘述了。

结语

以上就是笔者能想到的最简单的音乐播放器,重点是如何实现和其他主流音乐播放器,如网易云等相似的播放列表效果


  1. 百度百科词条·HTML5音频 ↩︎
  2. CSDN博客·使用HTML5和JavaScript创建音乐播放列表 ↩︎