搜索
首页web前端H5教程实现一个HTML5音乐播放器的实例

实现一个HTML5音乐播放器的实例

Jul 23, 2017 pm 01:28 PM
h5html5音乐

技术点: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 ? (&#39;0&#39; + tempMin) : tempMin;
            let curSec = tempSec < 10 ? (&#39;0&#39; + tempSec) : tempSec;return curMin + &#39;:&#39; + 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(&#39;GET&#39;,option.url);
            xhr.send(null);
        }
    };
    View Code

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

    let instance = false;

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

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

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

            if(instance){
                console.error(&#39;SKPlayer只能存在一个实例!&#39;);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(&#39;.skPlayer-cover&#39;),
                playbutton: this.root.querySelector(&#39;.skPlayer-play-btn&#39;),
                name: this.root.querySelector(&#39;.skPlayer-name&#39;),
                author: this.root.querySelector(&#39;.skPlayer-author&#39;),
                timeline_total: this.root.querySelector(&#39;.skPlayer-percent&#39;),
                timeline_loaded: this.root.querySelector(&#39;.skPlayer-line-loading&#39;),
                timeline_played: this.root.querySelector(&#39;.skPlayer-percent .skPlayer-line&#39;),
                timetext_total: this.root.querySelector(&#39;.skPlayer-total&#39;),
                timetext_played: this.root.querySelector(&#39;.skPlayer-cur&#39;),
                volumebutton: this.root.querySelector(&#39;.skPlayer-icon&#39;),
                volumeline_total: this.root.querySelector(&#39;.skPlayer-volume .skPlayer-percent&#39;),
                volumeline_value: this.root.querySelector(&#39;.skPlayer-volume .skPlayer-line&#39;),
                switchbutton: this.root.querySelector(&#39;.skPlayer-list-switch&#39;),
                modebutton: this.root.querySelector(&#39;.skPlayer-mode&#39;),
                musiclist: this.root.querySelector(&#39;.skPlayer-list&#39;),
                musicitem: this.root.querySelectorAll(&#39;.skPlayer-list li&#39;)
            };this.audio = this.root.querySelector(&#39;.skPlayer-source&#39;);if(this.option.listshow){this.root.className = &#39;skPlayer-list-on&#39;;
            }if(this.option.mode === &#39;singleloop&#39;){this.audio.loop = true;
            }this.dom.musicitem[0].className = &#39;skPlayer-curMusic&#39;;
        }
    View Code

    事件绑定,主要绑定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();
                    }
                });
            }
        }
    View Code

    至此,核心代码基本完成,接下来就是自己根据需要完成API部分。
    最后我们暴露模块:

    module.exports = skPlayer;

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

以上是实现一个HTML5音乐播放器的实例的详细内容。更多信息请关注PHP中文网其他相关文章!

声明
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn
了解H5代码:HTML5的基本原理了解H5代码:HTML5的基本原理Apr 17, 2025 am 12:08 AM

HTML5是构建现代网页的关键技术,提供了许多新元素和功能。1.HTML5引入了语义化元素如、、等,增强了网页结构和SEO。2.支持多媒体元素和,无需插件即可嵌入媒体。3.表单增强了新输入类型和验证属性,简化了验证过程。4.提供了离线和本地存储功能,提升了网页性能和用户体验。

H5代码:Web开发人员的最佳实践H5代码:Web开发人员的最佳实践Apr 16, 2025 am 12:14 AM

H5代码的最佳实践包括:1.使用正确的DOCTYPE声明和字符编码;2.采用语义化标签;3.减少HTTP请求;4.使用异步加载;5.优化图像。这些实践能提升网页的效率、可维护性和用户体验。

H5:网络标准和技术的发展H5:网络标准和技术的发展Apr 15, 2025 am 12:12 AM

Web标准和技术从HTML4、CSS2和简单的JavaScript演变至今,经历了显着的发展。 1)HTML5引入了Canvas、WebStorage等API,增强了Web应用的复杂性和互动性。 2)CSS3增加了动画和过渡功能,使页面效果更加丰富。 3)JavaScript通过Node.js和ES6的现代化语法,如箭头函数和类,提升了开发效率和代码可读性,这些变化推动了Web应用的性能优化和最佳实践的发展。

H5是HTML5的速记吗?探索细节H5是HTML5的速记吗?探索细节Apr 14, 2025 am 12:05 AM

H5不仅仅是HTML5的简称,它代表了一个更广泛的现代网页开发技术生态:1.H5包括HTML5、CSS3、JavaScript及相关API和技术;2.它提供更丰富、互动、流畅的用户体验,能在多设备上无缝运行;3.使用H5技术栈可以创建响应式网页和复杂交互功能。

H5和HTML5:网络开发中常用的术语H5和HTML5:网络开发中常用的术语Apr 13, 2025 am 12:01 AM

H5与HTML5指的是同一个东西,即HTML5。HTML5是HTML的第五个版本,带来了语义化标签、多媒体支持、画布与图形、离线存储与本地存储等新功能,提升了网页的表现力和交互性。

H5指的是什么?探索上下文H5指的是什么?探索上下文Apr 12, 2025 am 12:03 AM

H5referstoHTML5,apivotaltechnologyinwebdevelopment.1)HTML5introducesnewelementsandAPIsforrich,dynamicwebapplications.2)Itsupportsmultimediawithoutplugins,enhancinguserexperienceacrossdevices.3)SemanticelementsimprovecontentstructureandSEO.4)H5'srespo

H5:工具,框架和最佳实践H5:工具,框架和最佳实践Apr 11, 2025 am 12:11 AM

H5开发需要掌握的工具和框架包括Vue.js、React和Webpack。1.Vue.js适用于构建用户界面,支持组件化开发。2.React通过虚拟DOM优化页面渲染,适合复杂应用。3.Webpack用于模块打包,优化资源加载。

HTML5的遗产:当前了解H5HTML5的遗产:当前了解H5Apr 10, 2025 am 09:28 AM

HTML5hassignificantlytransformedwebdevelopmentbyintroducingsemanticelements,enhancingmultimediasupport,andimprovingperformance.1)ItmadewebsitesmoreaccessibleandSEO-friendlywithsemanticelementslike,,and.2)HTML5introducednativeandtags,eliminatingthenee

See all articles

热AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智能驱动的应用程序,用于创建逼真的裸体照片

AI Clothes Remover

AI Clothes Remover

用于从照片中去除衣服的在线人工智能工具。

Undress AI Tool

Undress AI Tool

免费脱衣服图片

Clothoff.io

Clothoff.io

AI脱衣机

AI Hentai Generator

AI Hentai Generator

免费生成ai无尽的。

热门文章

R.E.P.O.能量晶体解释及其做什么(黄色晶体)
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.最佳图形设置
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.如果您听不到任何人,如何修复音频
1 个月前By尊渡假赌尊渡假赌尊渡假赌
R.E.P.O.聊天命令以及如何使用它们
1 个月前By尊渡假赌尊渡假赌尊渡假赌

热工具

WebStorm Mac版

WebStorm Mac版

好用的JavaScript开发工具

记事本++7.3.1

记事本++7.3.1

好用且免费的代码编辑器

EditPlus 中文破解版

EditPlus 中文破解版

体积小,语法高亮,不支持代码提示功能

SublimeText3汉化版

SublimeText3汉化版

中文版,非常好用

VSCode Windows 64位 下载

VSCode Windows 64位 下载

微软推出的免费、功能强大的一款IDE编辑器