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

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

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

接下来就步入正题:

  1. 要做一个音乐播放器就要非常了解在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等等;

  2. 确定整体结构:
    因为自己是做成插件的方式发布在npm上供他人使用的,所以我们就采用面向对象的方式进行代码编写,又因为用户的需求不一,所以在设计之初就暴露出大量的API和配置项以满足大部分用户的需求。
    这里因为自己更习惯es6的语法,就全程以es6为基础进行开发,同时为了开发效率,又使用了sass进行css的编写,最后还使用了webpack和webpack-dev-server用以编译es6和sass,项目打包,构建本地服务器。
  3. 确定播放器UI和交互:
    可能关于界面每个人有自己的想法,这里就不过多赘述了,以我做好的播放器UI为例进行分解

    从界面中可以看出一个播放器所需要的最基础功能:
    播放/暂停、封面/歌名/歌手的显示、播放进度条/加载进度条/进度操作功能、循环模式切换、进度文字更新/歌曲时长、静音/音量大小控制、列表显示状态控制、点击列表项切歌功能
    再结合我们想要满足用户需求,提供配置项和API的出发点可以得出我们想设计的配置项和暴露的API项:
    配置项:自动播放是否开启、默认歌曲列表的显示状态、默认循环模式的设置
    API:播放/暂停/toggle、循环模式的切换、静音/恢复、列表显示状态的切换、上一曲/下一曲/切歌、销毁当前实例
  4. 确立项目结构,开始编码:
    因为使用webpack,所以我们直接将css打包至js内,以便作为插件供用户使用:
    require('./skPlayer.scss');

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

    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);
    }
    };

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

    let instance = false;

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

    class skPlayer {
    constructor(option){
    } template(){
    } init(){
    } bind(){
    } prev(){
    } next(){
    } switchMusic(index){
    } play(){
    } pause(){
    } toggle(){
    } toggleList(){
    } toggleMute(){
    } switchMode(){
    } destroy(){
    }
    }

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

            if(instance){
    console.error('SKPlayer只能存在一个实例!');
    return Object.create(null);
    }else{
    instance = true;
    }

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

            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();
    }
    });
    }
    }

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

    module.exports = skPlayer;

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

4个小时实现一个HTML5音乐播放器的更多相关文章

  1. 用<audio>标签打造一个属于自己的HTML5音乐播放器

    上一章节,我们刚刚讲了<video>标签,今晚,我们讲的是<audio>标签,这两个东东除了表示的内容不一样以外,其他的特性相似的地方真的太多了,属性和用法几乎一样,也就说,如 ...

  2. jqm视频播放器,html5视频播放器,html5音乐播放器,html5媒体播放器,video开展demo,html5视频播放演示示例,html5移动视频播放器

    最近看到很多有用的论坛html5视频播放的发展,音乐播放功能,大多数都在寻找答案.所以,我在这里做一个demo.对于大家互相学习.html5开发越来越流行,至于这也是一个不可缺少的一部分的视频. 如何 ...

  3. HTML5项目笔记4:使用Audio API设计绚丽的HTML5音乐播放器

    HTML5 有两个很炫的元素,就是Audio和 Video,可以用他们在页面上创建音频播放器和视频播放器,制作一些效果很不错的应用. 无论是视屏还是音频,都是一个容器文件,包含了一些音频轨道,视频轨道 ...

  4. 使用Audio API设计绚丽的HTML5音乐播放器

    HTML5 有两个很炫的元素,就是Audio和 Video,可以用他们在页面上创建音频播放器和视频播放器,制作一些效果很不错的应用. 无论是视屏还是音频,都是一个容器文件,包含了一些音频轨道,视频轨道 ...

  5. 简约的HTML5音乐播放器插件

    从我刚开始接触前端的时候就想写一个能播放音乐的小程序,刚开始写的时候虽然可以放,但是确实很慢,很卡,有很多可以优化的地方.最近在前一个版本的基础上重写了一个音乐播放器的插件,速度还可以吧 因为追求简约 ...

  6. [ 原创 ]学习笔记-做一个Android音乐播放器是遇到的一些困难

    最近再做一个安卓的音乐播放器,是实验室里学长派的任务,我是在eclipse上进行开发的,由于没有android的基础,所以做起来困难重重. 首先是布局上的困难 1.layout里的控件属性不熟悉 2. ...

  7. 一个开源音乐播放器,低仿QQ音乐!

    有暇,弄了个音乐播放器,页面效果整体上参考了QQ音乐,相关API使用了易源数据提供的相关接口(https://www.showapi.com/api/lookPoint/213),在此表示感谢.先来看 ...

  8. 一款好看+极简到不行的HTML5音乐播放器-skPlayer

    Demo: github skPlayer在线预览 预览: 单曲循环模式预览: 使用方法: 方式1:NPM npm install skplayer 方式2:引入文件 引入css文件: <lin ...

  9. HTML5音乐播放器(最新升级改造加强版)

    最近么,单位里面么老不顺心的,公司一直催要程序员要PHP,然后本宅好不容易推荐了一个,我日嘞,最后待遇变成1.3,吾师最后也同意1.3W,然后还说要考虑... 尼玛,4年多5年不到一点的工作经验,前端 ...

随机推荐

  1. opencv基础到进阶(1)

    Opencv是一个用户基础非常多的视觉开发库,可以用来实现人脸识别等功能,由于涉及到大量的调用与计算,所以对硬件的条件要求很高,并且还需要时时刻刻注意内存溢出这个问题,怎么样?很刺激吧. 从这篇文章开 ...

  2. 关于STM32空闲中断

    有一次做一个东西,为了尽量不占用CPU的处理数据时间,所以就使用DMA接收串口的数据,但是呢问题来了.,,,,,怎么样才能确定接收到了一条完整的数据了,,我们都知道只要打开DMA 那家伙就不停的把接收 ...

  3. #415 Div2 C

    #415 Div2 C 题意 给定一个数字集合,找到所有子集合最大值与最小值之差的和. 分析 列式子,找规律. $ (a_2 - a_1) * 2^0 + (a_3 - a_1) * 2^1 + .. ...

  4. JavaSE教程-04Java中循环语句for,while,do···while-练习2

    1.编写一个剪子石头布对战小程序 该法是穷举法:将所有情况列出来 import java.util.*; public class Game{ public static void main(Stri ...

  5. apache和nginx支持SSI配置

    今天发现了一种新的语言格式:.shtml 一. 前言 SSI是一种类似于ASP的基于服务器的网页制作技术.将内容发送到浏览器之前,可以使用"服务器端包含 (SSI)"指令将文本.图 ...

  6. python str的一些方法

    在python有各种各样的string操作函数.在历史上string类在python中经历了一段轮回的历史.在最开始的时候,python有一个专门的string的module,要使用string的方法 ...

  7. a标签点击之后有个虚线边框,怎么去掉

    1.行内处理方式1 <a hidefocus="true" href="#"></a> 2.行内处理方式2,让a标签获得焦点就失去焦点, ...

  8. ci框架学习告一段落,总结一下

    从网上弄了一张框架学习思维导图,从图中就可以看出ci用了设计模式中的MVC架构,使得用起来很简单方便 用了大概两个星期开发了一个<文章管理系统>觉得开发过程中学到了很多,挺不错的,而且在学 ...

  9. grunt中常见的插件

    /** * 需要用到的文件夹有 js(src) css image html */ gulp是一种自动化构建工具,可以增强我们的工作流程,他是基于 Node.js 构建的,与gruntjs相比,gul ...

  10. RabbitMQ分布式消息队列服务器(一、Windows下安装和部署)

    RabbitMQ消息队列服务器在Windows下的安装和部署-> 一.Erlang语言环境的搭建 RabbitMQ开源消息队列服务是使用Erlang语言开发的,因此我们要使用他就必须先进行Erl ...