原生 JavaScript 手写一款属于自己的音乐播放器

原生 JavaScript 手写一款属于自己的音乐播放器

先贴一下图 :

原生 JavaScript 手写一款属于自己的音乐播放器

如果您想线上观看此 demo 请戳这里 -> 线上demo

零、前言

1. 基础的布局【HTML + CSS】

原生 JavaScript 手写一款属于自己的音乐播放器

  • 因为笔者不会 CSS , 所以写的丑陋一些, 反正是最基础的布局与样式, 这些对于大家来说当然是信手拈来的啦, 唯一需要注意的就是这个歌词幕布的这个区域, 咱们的这块幕布中的内容是由歌词来进行填充的, 每一句歌词都是一个 li。
  • 这里不能将 li 的宽高定 si , 因为有的歌词一行就放下了, 有的需要两行甚至三行。定 si 的话就会导致格式样式所乱, 有的文字被挤下去影响了其他行。
  • 歌词幕布的层级应该在有着透明色的遮罩层的盒子之上, 在其直接父元素盒子之下, 父盒子设置溢出隐藏。这样当歌词幕布向上或向下移动的时候只会在指定区域显示当前比较重要的部分【譬如说需要高亮显示的区域部分】。

2. 歌曲请求方式及接口来源【ajax】

  • 因为歌曲本身笔者并没有放到本地, 而是将 3 首歌曲的相关信息写到了 json 文件中, 从 json 文件中获取歌曲的播放链接, 然后再直接怼到 video 上面就可以加载播放了【实际上这一步是将播放链接交给 video 然后由他去拿数据进行播放】。
  • 至于为什么要使用 video 进行播放的原因是从 QQ 音乐拿到的播放链接是 m4a 格式的, 使用 audio 无法播放。
  • 其实这个歌曲的播放来源地是哪并不重要, 重要的是咱们这次的主题播放器, 如果你想, 想听什么搜索什么, 你可以自己实现个后台用来搜索歌曲与解析信息【不是本次重点暂不详述】。

3. 数据上的准备

  • 在生成幕布中的歌词 li 时, 获取这个 li 集合放到全局以便使用【这个数据在这里起个别名叫 lrcLis】。
  • 在请求的数据回来的时候需要对当前渲染的数据进行解析, 将歌词中 [00:12:03]爱你一万年 这中格式的数据转换为一个数组集合对象, 就是数组中放每句歌词映射成的对象【[{time: xx, lrc: 'xx',}, ...]】(其中 time 属性存放的单位是秒也就是需要将 [分钟:秒:毫秒]这种格式转化为秒后存进去)。 并放到全局中以备使用【这个集合数据在这里起个别名叫 lrcs】。

综述 :

以上是在实现这个播放器之前自己的设想以及准备工作

一、播放器涉及到的功能模块

1. 点击歌曲 播放/暂停 按钮实现歌曲播放与暂停

此功能借助了 web video API 中的 paly()、pause() 方法来进行控制, 在播放的同时更改下图标即可。【但 play() 这个方法有个坑, 后面的踩坑攻略中详述 ~ 】

2. 实现歌词同步

  • 实现歌词同步借助了 web video API 中的 timeupdate 事件, 我们可以在此事件回调中写一些在播放位置改变时的逻辑, 譬如说歌词同步。
  • 具体的同步思想是 :
    • 歌词幕布随着歌曲播放而上下移动
    • 每次移动多少 ? 这个距离不是 si 的, 是根据每一个 li 的自身高度来决定的, 当然笔者在每一个 li 下面都放了一个 margin-top , 这个也要计算在内的【歌词幕布移动的偏移值 = li 自身的 height + margin-bottom】。
    • 在渲染之前, lrcLis 结构准备好以后则迭代这个 li 集合将其自身的height与margin-bottom之和放进 lrcs 集合去对应上 offset 属性, 这样每条 lrcs 集合中都有当前歌词自己的 time、lrc、offset 属性了。
    • 在全局维持一个变量 index 用来计数当前播放到第哪条数据的索引, 同时要维护一个 prev 用来存储上一次高亮的项, 也就说在本次高亮的时候将上一次高亮的元素的高亮效果取消。
    • 在每次歌曲播放位置改变, timeupdate 事件触发的时候, 累加 lrcs 中的 index 之前的所有项的 offset 的和。得到的这个值就是歌词幕布应该向上偏移的距离, 同时将 lrcLis 中的 index 代表的项【当前项】高亮即可。

3. 实现实时进度条与播放计时

  • 实时进度条

    原生 JavaScript 手写一款属于自己的音乐播放器

    • web video API 中有个 duration 属性可以获取当前播放音频的总时长
    • 由上图得知, 我们要想求 offset 偏移量就必须理清一个比例关系 : offset/width = currentTime/duration 所以可推出 offset =(currentTime * width ) / duration 。所以根据这个公式往里套值就可以得出进度条应该走的真实的距离【offset】, 然后修改对应样式即可。
  • 实时播放计时

    • 播放计时用到了 web video API 中的 currentTime 属性, 每次当歌曲播放位置改变的时候 currentTime 都会刷新, 利用这一点我们可以实现这个功能。

4. 实现点击进度条或者移动进度条滑块对歌曲进行控制

  • 首先点击这个很好实现, 利用上面的公式推导出点击后应该为 video 设置的 currentTime 属性值然后给 video 赋回去即可。currentTime = (offset * duration) / width

  • 移动滑块来控制与点击差不多但是多了几点

    • 利用 mousedown、mousemove、mouseup 来实现拖拽

    • 在 mousedown 的时候调用 pause() 暂停播放, 然后在 mouseup 时调用 play() 开启播放

      原生 JavaScript 手写一款属于自己的音乐播放器

5. 实现下载

  • 下载这个功能不借助后端来做的话, 通常做法是拿 a 标签直接怼, 但是有的像 zip 这种资源会直接下载, mp3 、png 这种就会直接打开,很不幸 m4a 也是直接打开的。但是如果想 mp3 等资源不打开而直接下载可以借助 a 标签的 download 属性, 但是这个只能在同源下用, 在跨域场景下依然失效。发 ajax 通过 blob实现 ? 也是跨域行不通的。所以这里笔者并没有去实现下载, 因为写这个 demo 的时候没打算写后台 ~ 。
  • 你可以自行起 node 服务先发送到本地 node 服务, node 服务那会资源直接返回前端这样利用 Proxy 原理可以在跨域场景下拿到数据并实现下载了。

6. 实现音量控制

  • 音量图标的静音与非静音的判断是借助 web video API 中的 muted 属性进行判断的,静音返回 true, 反之 false。

  • 修改 video 的音量大小是通过 web video API 中的 volume 属性来实现的 0代表静音 1 代表最大音量

  • 点击图标调节音量比较简单, 稍微复杂的就是进度条控制静音, 和上面一样也是一个比例关系 : 当前音量/1 = offset / width 可推出 当前音量 = (1 * offset) / width

7. 实现歌曲切换

  • 在左右按钮的点击事件回调中直接调用 render 方法重新渲染新的切换后的数据即可。

二、踩坑攻略

  • 在当前歌曲播放完毕再次点击播放的时候报错 Uncaught (in promise) DOMException: The play() request was interrupted by a call to pause(). 意思是说这个播放的请求被 pause 拦截掉了。并且这个 play() 执行返回的是一个 Promise 实例。

    想想上面哪个环节可能有问题, 最终定位到了实现拖拽控制进度播放的时候可能埋下隐患了, 因为 pause 是在 mousedown 的回调中执行的, play 是在 mouseup 的回调中执行的, 同时 mouseup 就在 mousedown 的回调里, 嵌套层级比 mousedown 更深了一层, 所以问题就出在了这个异步任务队列中。既然知道问题那就在mouseup 里面加了一层 setTimeout 。

  • 在歌词同步的时候, 由于有的歌曲中间会有一段旋律没有歌声那种情况, 那种情况恰恰是没有歌词的而我们生成的 li 结构中就会产生一些空内容的 li, 这些 li 的高度没法被撑开但是在歌词同步的时候又把这个 li 计算在内了, 就会导致同步不精确, 这个时候在生成结构的时候加层判断, 如果内容为空则给一个占位。

  • 在拖拽控制进度条播放 mouseup 是绑在 window 上了, 在歌曲第一次在涉及到移动滑块的情况下播放完了, 在 window 上就挂载了一个 up 事件, 以后点击随便一个地方 up 事件回调都会执行, 造成了一些副作用。这个可以基于事件源来搞定, 事先声明一个全局变量 target, 并在 window 上绑定一个 mousedown 事件, 事件回调里面将事件源对象赋给全局变量 target。在滑块的 mousedown 的时候也做同样的操作, 这样只需要在 mouseup 事件回调里面加层事件源的判断即可解决该问题。

  • 还有一些就是一些细小的细节问题但是总体上比较突出的问题是以上这几个。

三、需要注意

  • 由于播放链接请求的是 QQ 音乐的, 所以音乐版权属于 QQ 音乐官方。

  • QQ 音乐的播放链接大概有效期是 24 个小时, 过了就不能使用了, 但是每天早上 6:30 我会更新 git 仓库的 json 文件, 届时只需要将里面的数据拿过来覆盖掉原始的 json 即可。

  • 请求 QQ 音乐的服务器距离较远, 如果网速不好的话可能出现偶尔的缓冲级别的卡顿, 但是第一遍过后浏览器缓存后第二遍在听是非常的流畅的。因为笔者的这边网不好所以这样, 大家网好的可能不会出现这种情况。

  • 如果出现这种情况, 耐心一下初次加载给他点时间 ~

    原生 JavaScript 手写一款属于自己的音乐播放器

四、待实现的功能

这个 demo 首先的比较粗糙, 有很多功能来不及实现 :

  • 更加细致的歌曲切换。
  • 音质调节。
  • 频谱功能。
  • 以及一些 UI 方面的细节问题

五、尾声

  • 看到这里了, 说明您对这个 demo 题材是比较感兴趣的, 不知道以上所述是否对您产生了帮助。

  • 如果您有更好的 demo、更好的想法、更好的建议, 欢迎加我微信交流 : 【17803218829】

  • 参考文档 : web video API - 菜鸟教程

  • 如果您想线上观看此 demo 请戳这里 -> 线上demo

  • 项目地址 : git