技术点:ES6+Webpack+HTML5 Audio+Sass

这里,我们将一步步的学到如何从零去实现一个H5音乐播放器。

首先来看一下最终的实现效果:Demo链接 =>

界面:

skPlayer

接下来就步入正题:

要做一个音乐播放器就要非常了解在Web中音频播放的方式,通常都采用HTML5的audio标签

关于audio标签,它有大量的属性、方法和事件,在这里我就做一个大致的介绍。

属性:

src:必需,音频来源;

controls:常见,设置后显示浏览器默认的audio控制面板,不设置默认隐藏audio标签;

autoplay:常见,设置后自动播放音频(移动端不支持);

loop:常见,设置后音频将循环播放;

preload:常见,设置音频预加载(移动端不支持);

volume:少见,设置或返回音频大小,值为0-1之间的一个浮点数(移动端不支持);

muted:少见,设置或返回静音状态;

duration:少见,返回音频时长;

currentTime:少见,设置或返回当前播放时间;

paused:少见,返回当前播放状态,是否暂停;

buffered:少见,一个TimeRanges对象,包含已缓冲的时间段信息,即加载进度。该对象包含一个属性length,返回一个从0开始的数表示当前缓冲了多少段音频;还包含两个方法,start()、end(),分别需要传入一个参数,即传入音频已加载的第几段,从0开始。start()返回该段的起始时间,end()返回该段的终点时间。举例:即传入0,第一段的起始是0,终止时间是17,单位秒;

属性就介绍到这里,可能还有一些比较少用的属性如:playbackRate等,在视频播放中可能会用到,我就暂不讲解。

方法:

play():开始播放音频;

pause():暂停播放音频;

事件:

canplay:当前音频可以开始播放(只加载了部分buffered,并未全部加载完成);

canplaythrough:可以无停顿播放(即音频全部加载完成);

durationchange:音频时长发生变化;

ended:播放结束;

error:发生错误;

pause:播放暂停;

play:播放开始;

progress:音频下载过程中触发,事件触发过程中可以通过访问audio的buffered属性获取加载进度;

seeking:音频跳跃中触发,即为修改currentTime时;

seeked:音频跳跃完成时触发,即为修改完成currentTime时;

timeupdate:音频播放过程中触发,同时currentTime属性在同步更新;

事件就介绍到这里,可能还有一些不常用的事件暂不讲解。

最后再讲解一下 一个音频从开始加载到播放结束过程中,所触发的事件流以及我们在不同时间段可以操作的属性:

loadstart:开始加载;

durationchange:获取到音频时长(此时可以获取duration属性);

progress:音频下载中(将伴随下载过程一直触发,此时可以获取buffered属性);

canplay:所加载的音频足够开始播放(每次暂停后开始播放也会触发);

canplaythrough:音频全部加载完成;

timeupdate:播放过程中(currentTime属性伴随着同步更新);

seeking:修改当前播放进度中(即为修改currentTime属性);

seeked:修改当前播放进度完成;

ended:播放完成;

这就是整个音频的大致事件流,可能有一些少用的事件没有列举出。

在事件触发过程中,有一些属性在音频还没有开始加载的时候就可以设置,如:controls、loop、volume等等;

因为自己是做成插件的方式发布在npm上供他人使用的,所以我们就采用面向对象的方式进行代码编写,又因为用户的需求不一,所以在设计之初就暴露出大量的API和配置项以满足大部分用户的需求。

这里因为自己更习惯es6的语法,就全程以es6为基础进行开发,同时为了开发效率,又使用了sass进行css的编写,最后还使用了webpack和webpack-dev-server用以编译es6和sass,项目打包,构建本地服务器。

确定播放器UI和交互:

可能关于界面每个人有自己的想法,这里就不过多赘述了,以我做好的播放器UI为例进行分解

skPlayer

从界面中可以看出一个播放器所需要的最基础功能:

播放/暂停、封面/歌名/歌手的显示、播放进度条/加载进度条/进度操作功能、循环模式切换、进度文字更新/歌曲时长、静音/音量大小控制、列表显示状态控制、点击列表项切歌功能

再结合我们想要满足用户需求,提供配置项和API的出发点可以得出我们想设计的配置项和暴露的API项:

配置项:自动播放是否开启、默认歌曲列表的显示状态、默认循环模式的设置

API:播放/暂停/toggle、循环模式的切换、静音/恢复、列表显示状态的切换、上一曲/下一曲/切歌、销毁当前实例

确立项目结构,开始编码:

因为使用webpack,所以我们直接将css打包至js内,以便作为插件供用户使用:

require('./skPlayer.scss');

抽离公共方法,在播放器中有很多可能需要抽离的公共方法如:点击播放进度条和音量进度条时需要计算鼠标距离进度条左端的距离以进行进度跳转,时间从duration中获取到的以秒为单位的时间转换成标准时间格式等等:

const Util = {

leftDistance: (el) => {

let left = el.offsetLeft;

let scrollLeft;

while (el.offsetParent) {

el = el.offsetParent;

left += el.offsetLeft;

}

scrollLeft = document.body.scrollLeft + document.documentElement.scrollLeft;

return left - scrollLeft;

},

timeFormat: (time) => {

let tempMin = parseInt(time / 60);

let tempSec = parseInt(time % 60);

let curMin = tempMin < 10 ? ('0' + tempMin) : tempMin;

let curSec = tempSec < 10 ? ('0' + tempSec) : tempSec;

return curMin + ':' + curSec;

},

percentFormat: (percent) => {

return (percent * 100).toFixed(2) + '%';

},

ajax: (option) => {

option.beforeSend && option.beforeSend();

let xhr = new XMLHttpRequest();

xhr.onreadystatechange = () => {

if(xhr.readyState === 4){

if(xhr.status >= 200 && xhr.status < 300){

option.success && option.success(xhr.responseText);

}else{

option.fail && option.fail(xhr.status);

}

}

};

xhr.open('GET',option.url);

xhr.send(null);

}

};

由于设计之初,考虑到播放器的独特性,设计为只能存在一个实例,设置了一个全局变量以判断当前是否存在实例:

(实例判断,如果存在返回无原型的空对象,因为ES6构造函数内默认返回带原型的实例)

let instance = false;

if(instance){

console.error('SKPlayer只能存在一个实例!');

return Object.create(null);

}else{

instance = true;

}

在使用ES6的情况下,我们将主逻辑放在构造函数内部,将通用性强和API放在公共函数内部:

class skPlayer {

constructor(option){}

template(){}

init(){}

bind(){}

prev(){}

next(){}

switchMusic(index){}

play(){}

pause(){}

toggle(){}

toggleList(){}

toggleMute(){}

switchMode(){}

destroy(){}

}

初始化配置项,默认配置与用户配置合并:

const defaultOption = {

...

};

this.option = Object.assign({},defaultOption,option);

将常用属性绑定在实例上:

this.root = this.option.element;

this.type = this.option.music.type;

this.music = this.option.music.source;

this.isMobile = /mobile/i.test(window.navigator.userAgent);

一些公共的API内部this指向在默认情况下指向实例,但是为了减少代码量,将操作界面上的功能与API调用一套代码,在绑定事件的时候this指向会改变,所以通过bind的方式绑定this,当然也可以在绑定事件的时候使用箭头函数:

this.toggle = this.toggle.bind(this);

this.toggleList = this.toggleList.bind(this);

this.toggleMute = this.toggleMute.bind(this);

this.switchMode = this.switchMode.bind(this);

接下来,我们就使用ES6字符串模板开始生成HTML,插入到页面中:

this.root.innerHTML = this.template();

接下来初始化,初始化过程中将常用DOM节点绑定,初始化配置项,初始化操作界面:

this.init();

init(){

this.dom = {

cover: this.root.querySelector('.skPlayer-cover'),

playbutton: this.root.querySelector('.skPlayer-play-btn'),

name: this.root.querySelector('.skPlayer-name'),

author: this.root.querySelector('.skPlayer-author'),

timeline_total: this.root.querySelector('.skPlayer-percent'),

timeline_loaded: this.root.querySelector('.skPlayer-line-loading'),

timeline_played: this.root.querySelector('.skPlayer-percent .skPlayer-line'),

timetext_total: this.root.querySelector('.skPlayer-total'),

timetext_played: this.root.querySelector('.skPlayer-cur'),

volumebutton: this.root.querySelector('.skPlayer-icon'),

volumeline_total: this.root.querySelector('.skPlayer-volume .skPlayer-percent'),

volumeline_value: this.root.querySelector('.skPlayer-volume .skPlayer-line'),

switchbutton: this.root.querySelector('.skPlayer-list-switch'),

modebutton: this.root.querySelector('.skPlayer-mode'),

musiclist: this.root.querySelector('.skPlayer-list'),

musicitem: this.root.querySelectorAll('.skPlayer-list li')

};

this.audio = this.root.querySelector('.skPlayer-source');

if(this.option.listshow){

this.root.className = 'skPlayer-list-on';

}

if(this.option.mode === 'singleloop'){

this.audio.loop = true;

}

this.dom.musicitem[0].className = 'skPlayer-curMusic';

...

}

事件绑定,主要绑定audio的事件以及操作面板的事件:

this.bind();

bind(){

this.updateLine = () => {

let percent = this.audio.buffered.length ? (this.audio.buffered.end(this.audio.buffered.length - 1) / this.audio.duration) : 0;

this.dom.timeline_loaded.style.width = Util.percentFormat(percent);

};

// this.audio.addEventListener('load', (e) => {

// if(this.option.autoplay && this.isMobile){

// this.play();

// }

// });

this.audio.addEventListener('durationchange', (e) => {

this.dom.timetext_total.innerHTML = Util.timeFormat(this.audio.duration);

this.updateLine();

});

this.audio.addEventListener('progress', (e) => {

this.updateLine();

});

this.audio.addEventListener('canplay', (e) => {

if(this.option.autoplay && !this.isMobile){

this.play();

}

});

this.audio.addEventListener('timeupdate', (e) => {

let percent = this.audio.currentTime / this.audio.duration;

this.dom.timeline_played.style.width = Util.percentFormat(percent);

this.dom.timetext_played.innerHTML = Util.timeFormat(this.audio.currentTime);

});

//this.audio.addEventListener('seeked', (e) => {

// this.play();

//});

this.audio.addEventListener('ended', (e) => {

this.next();

});

this.dom.playbutton.addEventListener('click', this.toggle);

this.dom.switchbutton.addEventListener('click', this.toggleList);

if(!this.isMobile){

this.dom.volumebutton.addEventListener('click', this.toggleMute);

}

this.dom.modebutton.addEventListener('click', this.switchMode);

this.dom.musiclist.addEventListener('click', (e) => {

let target,index,curIndex;

if(e.target.tagName.toUpperCase() === 'LI'){

target = e.target;

}else{

target = e.target.parentElement;

}

index = parseInt(target.getAttribute('data-index'));

curIndex = parseInt(this.dom.musiclist.querySelector('.skPlayer-curMusic').getAttribute('data-index'));

if(index === curIndex){

this.play();

}else{

this.switchMusic(index + 1);

}

});

this.dom.timeline_total.addEventListener('click', (event) => {

let e = event || window.event;

let percent = (e.clientX - Util.leftDistance(this.dom.timeline_total)) / this.dom.timeline_total.clientWidth;

if(!isNaN(this.audio.duration)){

this.dom.timeline_played.style.width = Util.percentFormat(percent);

this.dom.timetext_played.innerHTML = Util.timeFormat(percent * this.audio.duration);

this.audio.currentTime = percent * this.audio.duration;

}

});

if(!this.isMobile){

this.dom.volumeline_total.addEventListener('click', (event) => {

let e = event || window.event;

let percent = (e.clientX - Util.leftDistance(this.dom.volumeline_total)) / this.dom.volumeline_total.clientWidth;

this.dom.volumeline_value.style.width = Util.percentFormat(percent);

this.audio.volume = percent;

if(this.audio.muted){

this.toggleMute();

}

});

}

}

最后我们暴露模块:

module.exports = skPlayer;

至此,核心代码基本完成,接下来就是自己根据需要完成API部分,详细部分移步至我的github查看源码。

一个HTML5音乐播放器就大功告成了 ~ !

html中加入音乐播放器,4个小时实现一个HTML5音乐播放器相关推荐

  1. java写一个音乐播放器源码_求一个JAVA音乐播放器的源代码

    展开全部 import javax.media.ControllerEvent; import javax.media.ControllerListener; import javax.media.E ...

  2. html制作自动切换音乐按钮代码,HTML5+JavaScript+CSS实现音乐播放器——难点二:自己设计一个控制音乐播放的控制器...

    我们都知道HTML5给我们提供了"controls"这个插件,可是这个插件却比较丑,还不能实现上一首下一首的播放,以及进度条的手动改变等功能,那么如何自己设计一个控制音乐播放的控制 ...

  3. 如何快速做一个HTML5移动播放器

    这段时间公司一直在做一个PC的教育类单页应用,庞大复杂,涉及非常多H5的知识,音频就是其中的一部分.前些天偷台风的闲暇时写了一个移动音乐播放器,作为练手项目(存放在码云).若你觉得该文章对你有帮助,别 ...

  4. python写一个文件下载器_Python3使用TCP编写一个简易的文件下载器

    原标题:Python3使用TCP编写一个简易的文件下载器 利用Python3来实现TCP协议,和UDP类似.UDP应用于及时通信,而TCP协议用来传送文件.命令等操作,因为这些数据不允许丢失,否则会造 ...

  5. python写一个文件下载器_python使用tcp实现一个简单的下载器

    上一篇中介绍了tcp的流程,本篇通过写一个简单的文件下载器程序来巩固之前学的知识. 文件下载器的流程如下: 客户端: 输入目标服务器的ip和port 输入要下载文件的名称 从服务器下载文件保存到本地 ...

  6. html5 video 选择音轨,触发一个HTML5音轨播放时,它已经加载

    如果加载成功,无论是使用src属性还是使用源元素,那么随着数据的下载,会触发进度事件.当加载足够的数据来确定视频的尺寸和持续时间时,会触发加载的元数据事件.当足够的数据被加载到渲染帧时,loadedd ...

  7. 复仇者联盟用的什么渲染器_我们正在构建一个惊奇目录阅读器! 复仇者联盟!

    复仇者联盟用的什么渲染器 In this tutorial, we're going to take a look at the Marvel API, a tool provided by Marv ...

  8. python编写下载器可暂停_Python编写一个优美的下载器

    本文实例为大家分享了python编写下载器的具体代码,供大家参考,具体内容如下 #!/bin/python3 # author: lidawei # create: 2016-07-11 # vers ...

  9. python多线程下载器_用 python 实现一个多线程网页下载器

    学习之 #!/usr/bin/env python # -*- coding:utf-8 -*- import urllib, httplib import thread import time fr ...

最新文章

  1. 娃哈哈信息部李钒助阵FBS2017 共探食品饮料信息化之路
  2. oracle 数据库备份恢复
  3. 服务的协作:服务间的消息传递——《微服务设计》读书笔记
  4. jq 控制td只显示一行_CSS让表格里的内容强制显示一行,超出长度显示省略号
  5. Java简单记事本设计实验报告_基于JAVA的记事本设计报告.doc
  6. mysql update upper_MySQL数据处理函数upper、abs、date
  7. Linux下chkconfig命令介绍
  8. 推荐网络带宽控制软件Netlimiter
  9. 6C.项链(C++)
  10. psql屏幕输出全部结果_液晶电视无法开机,是电源板问题还是屏幕问题,自己动手维修...
  11. 金蝶K/3 Cloud 实施笔记
  12. 强国的语言与语言强国
  13. 几张趣图带你了解程序员眼中的世界
  14. 付费系列 2 - 美式和百慕大期权 PDE 有限差分
  15. mysql入门最全参考笔记
  16. Error while executing topic command:KeeperErrorCode=NoNode for /brokers/ids
  17. 移动端系统生物认证技术详解
  18. 鼎捷T100开发杂记
  19. Windows搭建RTMP服务器+OBS推流+VLC拉流
  20. 深度学习导航(一)——神经网络的定义和基本概念

热门文章

  1. 【调剂】重庆医科大学生命科学研究院侯宇教授,招生调剂硕士生一名
  2. SSM实战项目_青橙商城学习笔记和踩坑指南
  3. C++ 标准库 条件变量:condition_variable、condition_variable_any
  4. 魔兽怀旧最新开服务器,魔兽世界:怀旧服“第八大区”上线,近期或开放更多新服务器...
  5. 2021水利水电安全员模拟考试案例题库及答案(二)
  6. npm 报错ERR Host key verification failed.
  7. 足不出户办理ETC:支付宝ETC服务
  8. el-admin前后端项目二次开发自定义修改图标
  9. 基于python的排课表系统_利用python爬取广西科技大学教务管理信息系统班级课表...
  10. 高德地图H5定位,搜索列表-完整代码