马黑黑 发表于 2022-10-8 19:45

手把手教小白用JS做一个简单实用的音频播放器

本帖最后由 马黑黑 于 2022-10-9 07:28 编辑

本文通过一个简单的音频播放器实例,手把手教小白用JS制作有限可控的音频播放器,高手请绕道。下面进入正文。

web页所用到的音频播放器,可以通过JS动态创建,非常简单。试看:

let aud = new Audio();
aud.src = '1.mp3';

音频播放器已经准备就绪!我们来理解一下代码的意思:

第一句,aud 是我们声明的变量,它是 Audio 对象的实例化。Audio 是一个开放的音频对象接口,JS 使用 new Audio() 语句就可以将之实例化并将操作权限赋予所指向的变量(例如这里的 aud),然后通过操作实例化变量——我们可以称之为播放器——来操控音频。

第二句,给 aud 播放器指定要播放的音频文件。它可以来自网络的URL,也可以是本地文件(如果web页运行于本地的话)。

虽然 aud 已做好准备,但音乐还没有播放。要令音乐播放,方法之一是加入控制按钮,我们可以事先在HTML代码部分写好它:

<button id="btnplay">播放</button>

这个 button 按钮,有 id 标识,JS直接通过其 id 名称操作它的单击事件:

btnplay.onclick = () => aud.play();

上面,我们使用了箭头函数,如果觉得箭头函数不好懂,上句可以写成下面的写法,二者是等价的:

btnplay.onclick = function() {
      aud.play();
}

这里,function 声明了一个匿名函数,就是函数没有函数名,它仅为 btnplay 的 onclick 事件即单击事件设置要操作的内容。play() 是 audio API 的内置指令,播放的意思,其载体是 aud,是下令给 aud 要播放音频。本文马上还要用到的指令还有 pause(),暂停。这些都是语义化的方法名,好懂好记。

OK,梳理一下代码,让播放器响起来:

<button id="btnplay">播放</button>

<script>

let aud = new Audio();
aud.src = '1.mp3';

btnplay.onclick = () => aud.play();

</script>

且慢,音乐怎么才能停下了?嗯,播放完了,它自然就停的……开玩笑的,我们既然可以通过JS给播放器下达播放指令,当然也可以下达暂停指令,前面说到过的 pause() 这里就快能用上了。为了简洁,我们不想再增设另外一个按钮,那么,播放、暂停音乐的任务就全都交给 btnplay 按钮吧,为此,我们需要加一个布尔变量 mFlag 用以记录音乐的播放与暂停状态:

let mFlag = false;

可以把它和前面声明 aud 的语句合并起来,以免代码中有太多的行数:

let mFlag = false, aud = new Audio();

mFlag 变量初始值为 false,因为这时音乐还没有播放。接着我们写一个函数,通过判断和改变 mFlag 的值作为驱动逻辑去处理 btnplay 单击事件的功能:

function mplay() {
      if(mFlag) {
                aud.pause();
                btnplay.innerText = '播放';
      } else {
                aud.play();
                btnplay.innerText = '暂停';
      }
      mFlag = !mFlag;
}

function 是创建自定义函数的关键字,mplay 是我们定义的函数名。创建非匿名函数的语句格式如下:

function 函数名() {
      //处理代码
}

我们回到业已创建的 mplay 函数。里面用了一个 if ... else ... 条件判断语句,判断的依据是 mFlag 变量:如果 mFlag 为真(if(mFlag) 等同于 if(mFlag === true)),此时音乐播放中,则,令音乐暂停,aud.pause();,同时令按钮改变表象名称为“播放”,btnplay.innerText = '播放';;否则,如果为假,此时音乐可能还没有播放或处于暂停状态中,则,令音乐播放,aud.play();,且更改按钮表象名称为“暂停”,btnplay.innerText = '暂停';。函数的最后,非常重要的,mFlag 变量要取反,mFlag = !mFlag;,表明每一次执行此函数,mFlag 的值都必须在真和假之间变换,之前是假的就变为真,之前是真的就变为假,以确保操作逻辑的准确性;取反的操作方法之一是小角感叹号(!)加在布尔变量的前面。

mplay 函数可以使用三元运算符将其简化写法:

function mplay() {
      mFlag ? (aud.pause(), btnplay.innerText = '播放') : (aud.play(), btnplay.innerText = '暂停');
      mFlag = !mFlag;
}

三元运算符里,每一个条件下要执行的语句为多句时,用小角括号将它们包裹起来,语句之间用小角逗号隔开。

函数写好后,btnplay 按钮的单击事件直接调用它:

btnplay.onclick = () => mplay();

我们重新组织一下改进后的播放器代码:

<button id="btnplay">播放</button>

<script>

let mFlag = false, aud = new Audio();
aud.src = '1.mp3';

btnplay.onclick = () => mplay();

function mplay() {
      mFlag ? (aud.pause(), btnplay.innerText = '播放') : (aud.play(), btnplay.innerText = '暂停');
      mFlag = !mFlag;
}

</script>

就这样,短短的几行代码,我们就已经创建了一个web页能使用的播放器,具备了最起码的音乐的播放与暂停功能,在此基础上,我们可以进一步令之丰富与完善,不过这是题外话。现在,我们要讨论的问题还有两个:

一、关于音频的自动播放

HTML5的 audio 控件实际上支持音频的自动播放,autoplay 属性可以用于HTML代码的实体标签,其值等于自身,JS对应的值则为 true(若设为不自动播放则为 false)。现代浏览器出于某些伦理机制,对声音做了限制,默认情况下,浏览器是不支持声音的自动播放的,所以,要确保 audio 的 autoplay 起作用,有下面两个依赖性,任选其一即可实现音频的自动播放:其一访问者的浏览器已经人为开启了自动播放声音或将本域名放入了声音自动播放的白名单,其二,通过HTML或JS代码设置 audio 为静音(muted 或 JS 的 muted = true)。

音频自动播放的依赖条件中,前者代码无能为力,后者则没有意义,故此,autoplay 如果设置为 autoplay(true),不要想当然地认为音乐就可以自动播放了。有些朋友的音画帖没有提供音频的控制按钮,部分访问者将听不到音乐,这是不明智的做法。可见,播放、暂停按钮还是很重要且很有必要的。

二、关于变量、函数的全局性与私有性

本文演示的代码,变量和函数都是全局性的,这意味着,在同一个页里,在其它地方,比如在本楼的任意一个楼层,可以通过 JS 操作我们编写的 aud 播放器,甚至改变按钮的名称和点击行为!考虑一下如下代码:

btnplay.onclick = function() {
      aud.src = '2.mp3';
      mplay();
      setTimeout(function() {
                btnplay.innerText = 'haha';
                btnplay.disabled = true;
      },4000);
}

这是干啥呢?首先,它改变了 aud 的歌曲,aud.src = '2.mp3';,接着它运行一下 mplay() 函数,然后它加入一个定时器,四秒钟以后将按钮表象名称改为“haha”,最后令按钮不可用。只要我们的代码后面存在类似这样的代码,我们前面编写的按钮的单击事件等都会被覆盖。演示放在二、三楼,为了不破坏二楼的效果,我在三楼另外做了一个按钮,实际上可以不做新按钮。

为了避免这类情况的发生,同时也是为了避免因全局变量、函数的可能同名现象而造成的彼此影响,从严谨角度看,我们还应该处理一下我们的播放器代码,一个最简洁的做法是,将JS代码封装成一个自执行匿名函数。自执行匿名函数语句格式为:

(function() {
      //函数体代码
}());

也可以写成:

(function() {
      //函数体代码
})();

自执行函数,不论匿名与否,它可以访问全局变量和函数,函数自身以外的东东则无法访问和控制器内部的变量与函数,且能保持私有变量的生命周期,是个不错的机制,这个机制,还多少与闭包有关,但话题超出了本文意图,不谈。最后,我们把前面写好的代码来个“闭包”——核心代码不变,只是将它们作为子内容用一个特殊结构的自执行匿名函数包裹起来:

<button id="btnplay">播放</button>

<script>

(function() {
      let mFlag = false, aud = new Audio();
      aud.src = '1.mp3';

      btnplay.onclick = () => mplay();

      function mplay() {
                mFlag ? (aud.pause(), btnplay.innerText = '播放') : (aud.play(), btnplay.innerText = '暂停');
                mFlag = !mFlag;
      }
}());

</script>

就酱。本文实际效果在二、三楼。

马黑黑 发表于 2022-10-8 19:45

本帖最后由 马黑黑 于 2022-10-8 19:54 编辑 <br /><br /><p>本楼使用未闭包的全局变量制作音频播放器,下一楼的按钮可以控制它。<br><br></p>

<p><button id="btnplay">播放</button></p>

<script>

let mFlag = false, aud = new Audio();
aud.src = 'https://music.163.com/song/media/outer/url?id=88875.mp3';

btnplay.onclick = () => mplay();

function mplay() {
        mFlag ? (aud.pause(), btnplay.innerText = '播放') : (aud.play(), btnplay.innerText = '暂停');
        mFlag = !mFlag;
}

</script>

马黑黑 发表于 2022-10-8 19:45

本帖最后由 马黑黑 于 2022-10-8 20:06 编辑 <br /><br /><p>本楼的按钮可以控制二楼的播放器: 换了一首歌,还改变自己的按钮名称(可以改变二楼的):<br><br></p>
<p><button id="btnplay1">哈哈</p>

<script>

btnplay1.onclick = function() {
        aud.pause();
        aud.src = 'https://music.163.com/song/media/outer/url?id=1336866553.mp3';
        aud.play();
        setTimeout(function() {
                btnplay1.innerText = 'Mars Song Now';
                btnplay1.disabled = true;
        },2000);
}

</script>

马黑黑 发表于 2022-10-8 19:45

本帖最后由 马黑黑 于 2022-10-8 20:53 编辑

当我们希望在条件许可时音乐应该自动播放(外加循环播放),那么,一楼提供的最终代码,需要改进一下:我们也不用 autoplay,而是直接给 aud 下达播放指令,同时取消掉 mFlag 变量——我们使用 aud.paused 属性取代它:

<button id="btnplay">播放</button>

<script>

(function() {
      let aud = new Audio();
      aud.src = '1.mp3';
      aud.loop = true;
      aud.play();
      btnplay.innerText = aud.paused ? '播放' : '暂停';
      
      btnplay.onclick = () => mplay();

      function mplay() {
                aud.paused ? (aud.play(), btnplay.innerText = '暂停') : (aud.pause(), btnplay.innerText = '播放');
      }
}());


红色的是新增的或已经发生了改变的代码。重点是 aud.play(),它的加入,使得:

当浏览器支持自动播放,则 aud.paused 的值为假,则按钮名称设置为“暂停”,反之,如果浏览器不支持自动播放,aud.paused 返回值是真,则按钮名称设置为“暂停”,今后的按钮单击事件也是依据 aud.paused 的返回值来决定音乐的播放暂停与按钮的名称。

红影 发表于 2022-10-8 20:37

前面的看看还能看懂点,到后面闭包就看糊涂了{:4_173:}

红影 发表于 2022-10-8 20:37

谢谢黑黑,如此细致入微的讲解。辛苦了{:4_187:}

马黑黑 发表于 2022-10-8 20:52

红影 发表于 2022-10-8 20:37
前面的看看还能看懂点,到后面闭包就看糊涂了

闭包仅是最终代码的头尾两行的事情,里面的代码不变。会用就好

马黑黑 发表于 2022-10-8 20:53

红影 发表于 2022-10-8 20:37
谢谢黑黑,如此细致入微的讲解。辛苦了

前面都是引路,重点在地板那一层

小辣椒 发表于 2022-10-8 21:14

这个可以直接做单曲播放器,音画里面可以用的

马黑黑 发表于 2022-10-8 21:55

小辣椒 发表于 2022-10-8 21:14
这个可以直接做单曲播放器,音画里面可以用的

对。啥也木有,就播放暂停。

小辣椒 发表于 2022-10-8 22:01

马黑黑 发表于 2022-10-8 21:55
对。啥也木有,就播放暂停。

这个是玩音画的福音

马黑黑 发表于 2022-10-8 22:05

小辣椒 发表于 2022-10-8 22:01
这个是玩音画的福音

按钮还是得改改样式

红影 发表于 2022-10-8 22:05

马黑黑 发表于 2022-10-8 20:52
闭包仅是最终代码的头尾两行的事情,里面的代码不变。会用就好

是完全陌生的,黑黑不说,之前一点都不知道{:4_173:}

马黑黑 发表于 2022-10-8 22:06

红影 发表于 2022-10-8 22:05
是完全陌生的,黑黑不说,之前一点都不知道

我过去使用的代码,有部分也是闭包的,多数没做

红影 发表于 2022-10-8 22:07

马黑黑 发表于 2022-10-8 20:53
前面都是引路,重点在地板那一层

哦,还有浏览器是否支持自动播放的判断呢。这个考虑得很周全了{:4_187:}

马黑黑 发表于 2022-10-8 22:09

红影 发表于 2022-10-8 22:07
哦,还有浏览器是否支持自动播放的判断呢。这个考虑得很周全了

它这个是必须的。很多人不会去设置浏览器的,都是使用默认的设置,这个时候,帖子不做这方面的逻辑判断是不对的,帖子没有交互按钮之类的东西更不对。

红影 发表于 2022-10-8 22:10

马黑黑 发表于 2022-10-8 19:45
当我们希望在条件许可时音乐应该自动播放(外加循环播放),那么,一楼提供的最终代码,需要改进一下:我们 ...

这楼的更简洁,要从前面一路看下来才能弄得明白{:4_187:}

马黑黑 发表于 2022-10-8 22:11

红影 发表于 2022-10-8 22:10
这楼的更简洁,要从前面一路看下来才能弄得明白

慢慢消化

小辣椒 发表于 2022-10-8 22:14

马黑黑 发表于 2022-10-8 22:05
按钮还是得改改样式
反正你完善了就可以玩了
黑黑我下了,晚安!

马黑黑 发表于 2022-10-8 22:15

小辣椒 发表于 2022-10-8 22:14
反正你完善了就可以玩了@素衣
黑黑我下了,晚安!

完善的代码在地板,按钮自己改
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: 手把手教小白用JS做一个简单实用的音频播放器