之前一个前端群里 大牛 做了一个自适应的HMLT5播放器
最近根据其思路做了一个相对单一移动端的demo,demo用的图片和歌曲json的数据设计 都是群里大牛做的,在这谢谢~;
同时借鉴的几篇文章:
慕课网 : http://www.imooc.com/view/299 (这个我也没看完....)
leinov : http://www.cnblogs.com/leinov/p/3896772.html (完整的音频的解释demo)
陈在真 :http://blog.sina.com.cn/s/blog_74d6cedd0102vkbr.html (也是这篇文章 让我在这次demo 里没有做音量控制和这个音量渐进、减退,因为感觉 没多大用....尤其是在手机端都有这个音控的按键前提下....)
上图:
上代码:
HTML:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>HTML5 播放器</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="description" content="">
<meta name="apple-touch-fullscreen" content="yes" />
<meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" name="viewport" />
<meta content="no" name="apple-mobile-web-app-capable" />
<meta content="black" name="apple-mobile-web-app-status-bar-style" />
<meta content="telephone=no" name="format-detection" />
<style>
*{margin:0;padding:0;}
li{ list-style: none;}
a{ text-decoration: none;}
html,body{width:100%; height:100%; font-family: 'microsoft yahei', Arial, Helvetica, sans-serif;}
body{-webkit-tap-highlight-color: rgba(0,0,0,0); }
.tac{ text-align:center;}
.fl{ float:left;}
.fr{ float:right;} .body_bg{transition:opacity 0.5s ease-in; position:absolute;left:0;top:0; width:100%; height:100%; background-image:url(bg.jpg) 0 0 no-repeat; background-size: cover; opacity: 0;}
.body_bg.cur{opacity:1;}
.list_main{overflow-y: auto;}
.list_ul{ width:100%;}
.list_ul li{ position:relative; min-height: 55px; font-size: 14px; padding:0 20px; color:#fff; line-height: 20px;}
.list_ul li.cur{ color:#f48e4a;}
.list_line{ position:absolute;left:0;bottom:0; width:100%; height:1px; background: rgba(0,0,0,.3);}
.list_ul .music_mian{ position:absolute;top:50%;transform: translateY(-50%);}
.list_ul p{ font-size: 14px;}
.audio_main{ position:fixed; bottom:0;left:0; width:100%; height:100px;background:rgba(0,0,0,.3); }
.process_line{ position:relative; width:100%; height:3px; background-color: #fff; }
.progress_after{ position:absolute;left:0;top:0; width:0px; height:3px; background-color: #f48e4a;}
.process_main{ }
.music_time{ color:#fff; font-size: 12px;margin:5px;}
.music_btn{ position:absolute;bottom:0;left:50%; width:238px; transform: translateX(-50%);}
.music_btn a{ display:inline-block; width:60px; height:46px; background:url(player.png) no-repeat 0 0; font-size: 0;}
.music_btn .music_before{background-position:-143px 0; }
.music_btn .music_switch{ background-position:-68px 0;margin-right:20px;}
.music_btn .cur{ background-position:-68px -140px;}
.music_btn .music_after{background-position:0px 0;margin-right:20px;} .name_singer{ color:#fff; margin:5px; font-size:12px;}
.audio_dot{ position:absolute;left:0;top:-6px; width:14px; height:14px;border-radius: 50%; background:#fff;}
.audio_Bgdot{ position:absolute;left:-15px;top:-15px; width:44px; height:44px;background:rgba(0,0,0,0);}
.turn{ position:absolute;left: -36px;bottom: 12px; width:30px;height:30px; text-align:center;line-height: 30px; font-size: 14px; color:#fff;}
</style>
<script type="text/javascript" src="zepto.js"></script>
<script type="text/javascript" src="music.js"></script> <script type="text/javascript">
$(function(){ var audios = [
{
name:'知己',
singer:'未知',
src:'02.mp3',
cover:''
},
{
name:'暗号',
singer:'周杰伦',
src:'01.mp3',
cover:'cover.jpg'
},
{
name:'One Day.mp3',
singer:'Kodaline',
src:'03.mp3',
cover:''
},
{
name:'KOK',
singer:'未知',
src:'KOK.mp3',
cover:''
},
{
name:'一块红布',
singer:'',
src:'04.mp3',
cover:''
},
{
name:'Hall Of Fame ',
singer:'The Script Feat Will.I.Am',
src:'05.mp3',
cover:''
},
{
name:'Rumour Has It',
singer:'未知',
src:'06.mp3',
cover:''
},
{
name:'寻找',
singer:'*',
src:'08.mp3',
cover:''
},
{
name:'Rooftops',
singer:'Kris Allen',
src:'07.mp3',
cover:''
}
]; Music.init(audios); }); </script>
</head> <body style="background-image:url(bg.jpg); opacity:1; ">
<div class="body_bg" style="background-image:url(bg.jpg); opacity:1; "></div>
<div class="list_main" id="list_main">
<ul class="list_ul" id="list_ul"> </ul>
</div>
<div class="audio_main" id="audio_main">
<div class="process_main">
<div class="process_line" id="process_line">
<div class="progress_after" id="line_after"></div>
<div class="audio_dot" id="dot">
<div class="audio_Bgdot"></div>
</div>
</div>
</div>
<div class="fl music_time" id="time_start">00:00</div>
<div class="fr music_time" id="time_end">00:00</div>
<div class="tac name_singer" id="name_txt"></div>
<div class="tac name_singer" id="singer_txt"></div>
<div class="music_btn">
<a href="javascript:;" class="music_after" id="music_after"></a>
<a href="javascript:;" class="music_switch" id="music_switch"></a>
<a href="javascript:;" class="music_before" id="music_before"></a>
<div class="turn" id="turn">顺</div>
</div>
</div>
</body>
</html>
js:
var Music = {
init : function( data ){
this.data = [];//存储数据;
this.randomArr = [];
for( var i=0,len=data.length;i<len;i++ ) {
if( data[i].src ){
this.data.push( data[i] );
this.randomArr.push(i);
}
} this._getElement(); //获取dom;
this.createAudio()//创建音频;
this.createMusicList(); //创建歌单; },
_getElement : function(){
this.list_ul = $('#list_ul');
this.music_switch = $('#music_switch');
this.music_before = $('#music_before');
this.music_after = $('#music_after');
this.time_start = $('#time_start');
this.time_end = $('#time_end');
this.process_line = $('#process_line');
this.line_after = $('#line_after');
this.window = $(window);
this.bodybg = $('.body_bg');
this.dot = $('#dot');
this.turn = $('#turn');
this.trunNum = 0;
this._dot = document.getElementById('dot');
this._process_line = document.getElementById('process_line');
this.Time = null;
this.startPic = this.bodybg.css('backgroundImage');
this.dragBtn = false;
this.playing = false;
},
_bindElement : function(){
var that = this;
var that = this; this.turn.click(function(){
that.trunNum++; var status = that.trunNum % 3;
switch(status){
case 0:
that.turn.html('顺');
break;
case 1:
that.randomArr.sort(function(){
return Math.random()>0.5?-1:1;
});
that.turn.html('随');
break;
case 2:
that.turn.html('单');
break;
}
}); //下一首歌
this.music_before.click(function(){
that._beforeMusic();
}); //上一首歌
this.music_after.click(function(){
that._afterMusic()
}); this.music_switch.on('click',function(){
that._fnPlay();
}); this.lineW = this.process_line.width() - this.dot.width();
function _touchstart( event ){ that.dragBtn = true; that.maxX = that.process_line.width() - that.dot.width(); event.stopPropagation();
} function _touchmove( event ){
var touch = event.targetTouches[0];
var moverX = touch.pageX;
that.dragBtn = true;
moverX = moverX < 0 ? 0 : moverX;
moverX = moverX > that.maxX ? that.maxX : moverX; that.dot.css('left',moverX);
that.line_after.css('width',moverX);
that.dragX = touch.pageX;
event.stopPropagation();
} function _touchend( event ){
var touch = event.targetTouches[0];
event.stopPropagation();
_tapLine();
}
this.process_line.click(function(event){
var eX = event.clientX > that.lineW ? that.lineW : event.clientX; that.dot.css('left',eX);
that.line_after.css('width',eX);
_tapLine();
});
function _tapLine(){
var l = parseFloat(that.dot.css('left'));
var maxL = parseFloat(that.lineW);
that._audio.currentTime = that._percentage( l,maxL,that._audio.duration);
clearInterval(that.Time);
that._musicTimer();
that.dragBtn = false;
} this._dot.addEventListener('touchstart',_touchstart,false);
this._dot.addEventListener('touchmove',_touchmove,false);
this._dot.addEventListener('touchend',_touchend,false);
},
createAudio : function(){
var html = '<audio id="audio" >您的浏览器不支持音频元素。</audio>';
$('body').append(html);
this._audio = document.getElementById('audio');
},
createMusicList : function(){
var that = this;
var html = '';
var data = this.data;
this._getHeight();
for( var i=0,len=data.length;i<len;i++ ){
var name,singer,src,pic; if( toStrings(data[i].name).length > 0){
name = toStrings(data[i].name);
}else{
name = '未知';
}
if( toStrings(data[i].singer).length > 0){
singer = toStrings(data[i].singer);
}else{
singer = '未知';
} /*
if( data[i].cover ){
pic = data[i].cover;
}else{
pic = this.startPic;
}
*/
pic = data[i].cover;
src = data[i].src; html += '<li music_index='+i+' music_name='+ name +' music_singer='+ singer +' music_src='+src+' music_pic='+ pic +'>'
+'<div class="music_mian">'
+ '<p class="music_name">'+name+'</p>'
+ '<p class="music_singer">'+singer+'</p>'
+ '</div>'
+ '<div class="list_line"></div>'
+'</li>'; }
this.list_ul.html(html); //列表绑定事件;
this.list_ul.on('click','li',function(){ that._bindElement();//创建事件;
that._cutSongs(this); }); //转换字符去掉空格;
function toStrings( txt ){
return txt.split(' ').join(' ').toString();
}
},
_afterMusic : function(){
var status = this.trunNum % 3;
var current = this.list_ul.find('.cur');
var index = current.index();
var maxLen = this.data.length-1;
var obj; if( status == 0 || status == 2 ){
if( index == 0 ){ obj = this.list_ul.find('li').last(); }else if( index > 0 ){ obj = current.prev(); }
}else if( status == 1 ){
for(var i=0;i<maxLen;i++){
if( this.randomArr[i] == index ){
var num = i;
num--;
if( num <= 0 ){
num = maxLen;
}
obj = this.list_ul.find('li').eq(this.randomArr[num]);
}
}
}
this._cutSongs(obj);
},
_beforeMusic : function(){
var status = this.trunNum % 3;
var current = this.list_ul.find('.cur');
var index = current.index();
var maxLen = this.data.length-1;
var obj; if( status == 0 || status == 2 ){
if( index == maxLen ){ obj = this.list_ul.find('li').first(); }else if( index < maxLen ){ obj = current.next(); }
}else if( status == 1 ){
for(var i=0;i<maxLen;i++){
if( this.randomArr[i] == index ){
var num = i;
num++;
if( num >= maxLen ){
num = 0
}
obj = this.list_ul.find('li').eq(this.randomArr[num]);
}
}
}
this._cutSongs(obj);
},
_cutSongs : function( obj ){
this.playing = true;
$(obj).addClass('cur').siblings('li').removeClass('cur');
var music_src = $(obj).attr('music_src');
var music_pic = $(obj).attr('music_pic');
var music_name = $(obj).attr('music_name');
var music_singer = $(obj).attr('music_singer'); $('#name_txt').html(music_name);
$('#singer_txt').html(music_singer); if( music_pic ){
this.bodybg.attr('style','background-image:url('+music_pic+');' );
}else{
this.bodybg.css('backgroundImage',this.startPic+';');
}
this.bodybg.addClass('cur'); this._audio.src = music_src; this._fnPlay();
},
_getHeight :function(){
var wH = $(window).height();
var mH = $('#audio_main').height();
var $musicMain = $('#list_main');
var h = wH - mH;
$musicMain.height(h); },
_fnPlay : function(){
var that = this; if( this._audio.paused ){ //判断是否重新加载歌曲
if(this.playing){
this._audio.load(); this.playing = false;
} this._audio.play();
this.music_switch.addClass('cur');
this._audio.addEventListener('loadeddata',function(){
var timerMain = that._toTime(that._audio.duration);
var len = timerMain.split(':').length; switch(len){
case 1:
that.time_start.html('00');
break;
case 2:
that.time_start.html('00:00');
break;
} that.time_end.html(timerMain);
that._musicTimer(); },false); }else{
this._audio.pause();
this.music_switch.removeClass('cur');
clearInterval(that.Time);
} },
_percentage:function( a,b,c ){
return a/b*c;
},
_musicTimer : function(){
var that = this;
this.Time = setInterval(_snowTime,1000);
var timerMain = that._toTime(that._audio.duration);
var len = timerMain.split(':').length;
function _snowTime(){ if( that._audio.currentTime == that._audio.duration ){
clearInterval(that.Time);
var status = that.trunNum % 3;
switch(status){
case 0:
that._beforeMusic();
break;
case 1:
that._beforeMusic();
break;
case 2:
var current = that.list_ul.find('.cur');
that._cutSongs(current);
break;
} } if(!that.dragBtn){
var timerMain = that._toTime(that._audio.currentTime);
var l = that._percentage( that._audio.currentTime,that._audio.duration,parseFloat(that.lineW) );
that.dot.css('left',l);
that.line_after.css('width',l);
} switch( len ){
case 1:
that.time_start.html(timerMain);
break;
case 2:
var timer = timerMain.indexOf(':')>0?timerMain:'00:'+timerMain;
that.time_start.html(timer);
break; } }
},
_toTime : function( time ){
var hour = Math.floor(time/3600);
var min = Math.floor(time%3600/60);
var sec = Math.floor(time%60); var ih = hour <= 9 ? '0'+hour : hour;
var im = min <= 9 ? '0'+min :min;
var is = sec <= 9 ? '0'+sec :sec;
return ( ih > 0 ? ih+':' : '') + (im >0 ? im+':' :'') +is;
}
}
总结:
最开始想用 面向对象的方式写,但是后来还是觉得json单体的写法更实用
个人简单粗暴的判断方法是:
页面 单一功能的应用 就用 json单体;
页面 某功能多次调用 就用 面向对象 比如: tab选项卡、轮播之类的;
hml5 audio 的新的api 文上的连接讲比较详尽就不赘述了
遇到的个人一些小问题:
一:第一次用这个 zepto.js 感觉跟 jQuery 一样,不过这个 tap 的事件怎么绑定不上 后来还是用 click 代替了 之前听说在 click 在移动端有延迟 也因为是用了 zepto 库还没绑定上完全用的跟 JQuery 似的也让我很尴尬,同时也因为用了库在 dom 的获取也用了原生所以在这个dom存储上有所难以区分(当然我知道这对象可以转成原生,最后还是用了纯原生的方式获取),因为 audio 有很多新的属性 都需要原生;
二:单体的this 指向 需要不停的 存成 that在用 有点繁琐;
三:歌曲时间想做到小时以上 比如 : 01:02:05 这样 后来 在这个当前播放时间设置 _snowTime() 里 判断一句就是 _toTime()返回 当前播放秒数转成分钟形式的字符串 (如:00:00:00)里的":"的个数,在时间补零(00:00:05)上 还要在用这个 ':'做判断觉得恶心,后来看了酷狗的播放器上过小时的也是用分钟的形式展示(120:30)目前还没想到什么好一点解决方法,也可以说是目前的一个已知 bug 吧;
四:最初的功能方法上想的比较独立单一,起初以为 “上一首歌”,“下一首歌”,和“歌曲播放到最后的下一首歌” 都是 独立的 其“歌曲播放到最后的下一首歌”其实之分两种一种是单曲循环,第二种是“下一首歌” 所以 “歌曲播放到最后的下一首歌” 区分不是 “单曲循环” 模式 剩下都是 “下一首歌” 里的功能 在“下一首歌”里做这个 歌曲模式“随机”、“顺序”播放的下一首就可是了;
五:在做这个“歌曲进度”拖动的时候,和这个歌曲正常播放 设置 “歌曲进度” 的冲突,后来建立that.dragBtn 判断是拖动还是仅仅是歌曲进度的设置,同样的问题还有这个 当前歌曲的暂停之后的播放和切割之后的播放 是有所不同的,歌曲的切换时是换了audio的路径 需要audio.load() 从新加载才能播放同样创建了this.playing 来判断是否切歌;
六:歌曲模式的设置 最开始想多了 想建立一个 json {‘顺’,‘随’,‘单’} 做对应关系,后来发现 其实只需要一个 num 做累加取余去判断即可;
功能总结:顺序播放、随机播放、单曲循环、上下换歌、点击切歌、拖动歌曲的进度;
没做功能:没有音控、手机转屏从新设置....没怎么做适配,仅仅适配自己的手机,肯定还有Bug和代码如何在优化欢迎指出;
谢谢 大牛们 写的技术文章做过的demo的共享~