转发我在github发布的一个H5音乐播放器
H5MusicPlayer GitHub链接 欢迎Fork & Star。
前言
- 这是我第一个GitHub项目,之前一直想在GitHub写点东西,近期又在学前端,刚好学到audio标签,平常时也比较喜欢听音乐写代码,因此就萌生了自己写一个音乐播放器的想法。利用了下班和周末的空闲时间,用了两周时间终于写出来了。当用代码一个个地把自己的想法实现出来,那种兴奋和成就感是无与伦比的。甚至周末的时候一码就码到通宵,还不觉得累哈哈。因为我也是初学,用GitHub把自己的注释和思路写出来,可以提供给跟我一样的伙伴来练手。
- 首先先要感谢两位大神的知识分享:
- 王乐平:CSDN博客
- CeuiLiSA:GitHub首页
该音乐播放器是基于王乐平的基础上修改的,借鉴了他的思路进行完善和添加功能。该博客的链接为一步一步实战HTML音乐播放器。实现的思路写得非常清晰,让我对写播放器摸着了门路。而CeuiLiSA则详细地分享了网易云最新的接口网易云音乐API,可以利用这些接口获得歌单、歌曲、歌词、专辑图片等,让播放器的功能更加完善。
音乐播放器效果
-
我把音乐播放器放到了腾讯云服务器了,可以直接点击下面链接查看效果,音乐播放器具备的功能基本都实现了。如当前歌单音乐列表、输入网易云用户名获取歌单进行切换、音乐列表歌名搜索、网络音乐搜索、电脑本地音乐添加播放、歌词滚动显示、歌曲播放进度等。
-
音乐播放器的整个界面效果:
-
从上到下的布局分为“音乐列表”“您的歌单”button按钮、专辑图片、歌名、歌手、歌词、播放控制按钮、歌曲时长、当前时间、播放进度条。
页面代码布局
<div class="player">
<!--播放器顶部,用来装载各种菜单button-->
<div class="header">
<button id="musicBtn" class="button" onclick="showList()">音乐列表</button>
<button id="back" class="button" onclick="goBack()" style="display: none;">返回</button>
<button id="local" class="localButton" onclick="getLocalFiles()" style="display: none;">本地音乐</button>
<button id="switch" class="localButton" onclick="myPrompt('绿水青山jv')">您的歌单</button>
<button id="search" class="searchButton" onclick="promptSearch()" style="display: none;">搜索</button>
<span id="title" style="display: block;" onmouseover="showShortcut(this,event)" onmouseout="showShortcutOut()">音乐播放器</span>
</div>
<div>
<!--音乐专辑图片-->
<div class="albumPic"></div>
<!--音乐列表-->
<div id="musicList" class="musicList">
<iframe style="width: 100%;" src="music.html"></iframe>
</div>
<!--歌单列表-->
<div id="songList" class="musicList">
<iframe style="width: 100%;" src="songList.html"></iframe>
</div>
<!--本地音乐搜索列表-->
<div id="searchMusicList" class="musicList">
<iframe style="width: 100%;" src="searchMusicList.html"></iframe>
</div>
<!--网络音乐搜索列表-->
<div id="netSearchList" class="musicList">
<iframe style="width: 100%;" src="netSearchList.html"></iframe>
</div>
<!--本地文件夹音乐列表-->
<div id="localMusicList" class="musicList">
<iframe style="width: 100%;" src="localMusicList.html"></iframe>
</div>
<!--歌词背景-->
<div id="lyricDiv" class="musicList"
style="width:320px;
background-image: linear-gradient( rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.2) ),url(img/clouds.jpeg);
background-repeat: no-repeat; border: none;padding: 0px;
text-align: center;
font-size: 20px;
color: #cbc7c7;
overflow: hidden;
position: relative;">
<ul>
<li>歌词</li>
</ul>
</div>
</div>
<!--音乐信息,歌名、歌手-->
<div class="trackInfo">
<div class="name"><p></p></div>
<div class="artist"></div>
<div class="album"></div>
</div>
<!--歌词-->
<div class="lyric">
<p id="lyric" onclick="showFullLyric()"
onmouseover="lyricTip(this,event)"
onmouseout="tipOut()">
歌词
</p>
</div>
<!--播放控制按钮,上一首下一首-->
<div class="controls">
<div class="play">
<i class="icon-play"></i>
</div>
<div class="previous">
<i class="icon-previous"></i>
</div>
<div class="next">
<i class="icon-next"></i>
</div>
</div>
<!--显示歌曲时间信息,当前播放时间、歌曲总时长-->
<div class="time">
<div class="current"></div>
<div class="total"></div>
</div>
<!--使用渐变颜色显示歌曲时间进度条-->
<div class="progress"></div>
<!--加载动画,正在使用网络加载音乐文件时则显示出来,完成则隐藏-->
<div class="loader"></div>
<div class="loader2"></div>
<!--audio标签装载当前播放的音乐文件-->
<audio id="audio" preload="auto"><source src=""></audio>
</div>
<!--装载用户之前输入的网易云用户名,帮助用户快捷输入-->
<datalist id="nameLists"></datalist>
<!--装载用户之前搜索输入的歌曲名,帮助用户快捷输入-->
<datalist id="songLists"></datalist>
- 除了主页面显示的元素,还隐藏着下面的元素:
- 这些元素会在按钮点击的情况触发显示,元素的隐藏和显示主要利用display的block或者none来控制。音乐列表的内容主要用iframe标签内嵌页面来显示。
播放器实现的逻辑思路
- 首先音乐播放器有三个重要的对象:
1、当前播放音乐:currentPlaySong
2、当前播放音乐列表:musicInfos
3、当前播放状态:playStatus - currentPlaySong的属性为:
{
songListName: ""; //所属的歌单名称
songID:""; //歌曲ID,网易云音乐每首歌都有固定的ID,通过ID可以获得歌曲的播放链接
songName: "";//歌名
artist: "";//歌手
albumPic: "";//专辑图片url
totalTime: "";//歌曲总时长
mp3Url: "";//该url的格式是http://music.163.com/song/media/outer/url?id=3986241
mp3Url2: "";//该url的格式是"https://m7.music.126.net/20181016104636/965ff036084dc30ec291460d1f4f85e3/ymusic/8827/447f/9ef4/3f399b1fd6d919555b55691e6632366d.mp3"
上面两个是不同的链接格式,第一个链接我公司的网络设置了禁止访问网易云音乐163网站,所以无法播放,但一般的网络都可以访问。
第二个链接则是通过网易云api接口获取到的,公司的网络没有禁止,可以访问,当然一般的网络也可以访问。
两个链接都同时添加到audio下的source下,一个链接失效了,另一个链接可以使用。
connectTimes: ""; //记录从网易云api接口获取了多少次mp3Url链接,超过一定的次数则停止获取,避免ID失效,死循环获取
index: "" //是在歌曲列表中的第几首歌
};
- musicInfos就是一个数组,存储列表中的所有音乐对象。
- playStatus的属性为:
{
currentTrackLen: 0; //当前播放列表总的歌曲数
currentTrackIndex: 0; //当前播放的是列表中的第几首歌
currentTime: 0; //当前播放的时长
currentTotalTime: 0; //当前播放歌曲的总时长
playTimes: 0; //点击上一首下一首的总次数,当达到10次就更新网易云接口获取的mp3Url链接,因为这个链接网易云设置了失效,30分钟左右就会失效
_playStatus: false; //使用下划线前缀表示这是受保护的对象,即protected
};
- 主要的流程为:
异步获取歌单列表的代码为:
//获取歌单的所有歌曲信息
function initSongs(url) {
var connectUrl = "";
if(url) {
connectUrl = url;
} else {
connectUrl = playlistUrl + playListID;
}
//通过ajax同步请求数据,如果网络异常则给出提示
$.ajax({
url: connectUrl,
type: "post",
dataType: 'JSON',
async: false,
cache: true,
success: getSongId,
error: function(xhr, status, error) {
//alert("抱歉,服务器故障了。\n你可以播放默认歌曲和本地音乐。");
//如果网络问题或者服务器挂了,则加载预先下载好的歌单信息
getSongId(playListTest);
}
});
}
//根据网易云接口返回的信息初始化歌曲列表
function getSongId(data) {
var tracks = data.playlist.tracks;
//刷新歌曲之前,先把之前的歌曲置空
musicInfos = [];
for(var i = 0; i < tracks.length; i++) {
var musicInfo = new Object();
//只在第一个对象中存储歌单名称
if(i == 0) {
musicInfo.songListName = data.playlist.name;
}
musicInfo.songID = tracks[i].id;
musicInfo.songName = tracks[i].name;
//为了简洁,只获取第一个歌手的名
musicInfo.artist = tracks[i].ar[0].name;
musicInfo.albumPic = tracks[i].al.picUrl + '?param=270y270';
musicInfo.totalTime = tracks[i].dt;
musicInfo.mp3Url = "http://music.163.com/song/media/outer/url?id=" + tracks[i].id + ".mp3";
musicInfo.mp3Url2 = ""; //第二个链接先置空,后面利用空闲时间异步获取,保证应用性能
//connetTimes记录当前歌曲从接口获取mp3Url连接的次数,
//超过5次则停止获取,
//避免歌曲ID失效,网易云接口传过来的是空url,造成浪费资源多次获取
musicInfo.connectTimes = 0;
musicInfo.index = i;
musicInfos.push(musicInfo);
}
//初始完成之后主要把赋值给正在播放的列表
currentPlayList = musicInfos;
currentPlayListIndex = 0;
//现在有了新的解决mp3Url的方法,就是获取到歌曲ID后直接
//在该链接后面加上ID的值http://music.163.com/song/media/outer/url?id= + id.mp3,
//这为获取url链接提供了极大的方便,
//既不用担心url链接失效,也不用担心获取url的接口失效或者IP被禁,
//也避免了多次耗费资源循环地更新url链接
//先同步获取第一首歌曲的第二个mp3Url2链接,
//后面才异步获取当前播放歌曲的前10首和后10首。
//由于网易云接口获取的url有时间限制,超过大概半个小时后url链接就会失效,
//所以用户点击上一首或下一首共10次之后或者半个小时之后就会更新链接
getMp3Url(musicInfos, 0, 1, 0, false);
}
//var errorCount = 0;
//initList当前需要获取url链接的数组,为musicInfos或者songSearchResults
function getMp3Url(initList, startindex, endIndex, goalIndex, isAsync, isCached) {
for(var i = startindex; i < endIndex; i++) {
var songUrl = "";
if(goalIndex) {
songUrl = "https://api.imjad.cn/cloudmusic/?type=song&id=" + initList[goalIndex].songID + "&br=128000";
} else {
songUrl = "https://api.imjad.cn/cloudmusic/?type=song&id=" + initList[i].songID + "&br=128000";
}
$.ajax({
url: songUrl,
type: "get",
dataType: 'JSON',
crossDomain: true,
async: isAsync,
cache: isCached ? isCached : isAsync,
success: function(data) {
if(isAsync) {
var times = 0;
while(times < initList.length) {
//为防止异步加载数据顺序错乱,只有songID对应时才添加mp3Url链接
if(initList[startindex].songID != data.data[0].id) {
startindex = (startindex + 1) % initList.length;
times++;
} else {
initList[startindex].mp3Url2 = data.data[0].url;
startindex = (startindex + 1) % initList.length;
break;
}
}
} else {
initList[goalIndex].mp3Url2 = data.data[0].url;
}
},
error: function(xhr, status, error) {
console.log(xhr.status);
//errorCount++;
}
});
}
}
初始化播放状态的代码为:
//当前播放器状态
var playStatus = {
currentTrackLen: 0,
currentTrackIndex: 0,
currentTime: 0,
currentTotalTime: 0,
playTimes: 0, //点击上一首下一首的次数
//使用下划线前缀表示这是受保护的对象,即protected
_playStatus: false,
};
function initPlayStatus() {
if(currentPlayList) {
playStatus.currentTrackLen = currentPlayList.length;
} else {
playStatus.currentTrackLen = 0;
}
playStatus.currentTrackIndex = 0;
playStatus.currentTime = 0;
//因为timgTask会不断读取音乐的currentTime,
//当点击本地文件夹音乐列表调用initPlayStatus()时,timgTask还会继续读取,
//导致currentTime > currentTotalTime,误以为当前歌曲已经播放完成,
//从而不断地播放一下首,导致bug
playStatus.currentTotalTime = 10000000;
playStatus.playTimes = 0;
playStatus._playStatus = false;
}
初始化前端界面代码为:
//播放器控制方法
playerControls = {
//歌曲基本信息
trackInfo: function(args) {
//保存现在正在播放的歌曲
currentPlaySong = args;
//先添加audio元素
$('#audio').remove();
$('.player').append('<audio id="audio" preload="auto"><source id="source1" src=""><source id="source2" src=""></source></audio>');
$('#source1').attr('src', args.mp3Url2);
$('#source2').attr('src', args.mp3Url);
//加载audio音频
$("#audio")[0].load();
if(args.isLocal) {
//如果是本地文件夹的歌曲,则在audio音频元素加载完成时进行读取音频时长
$("#audio")[0].onloadedmetadata = function() {
//音频时长单位为秒
var totalTime = $("#audio")[0].duration;
$('.player .time .total').text(timeConvert(totalTime));
playStatus.currentTotalTime = totalTime - 1;
args.totalTime = totalTime;
}
} else {
//网易云接口返回的音频时长单位为毫秒
//显示这首歌的时长,
$('.player .time .total').text(timeConvert(args.totalTime / 1000));
//减1是为了避免计算误差,无法自动下一首
playStatus.currentTotalTime = Math.floor(args.totalTime / 1000) - 1;
}
//根据歌名长度设置字体大小,避免歌名太长超出边框
$('.player .trackInfo .name p').text(args.songName);
if(args.songName.length >= 40) {
$('.player .trackInfo .name').css("font-size", "18px");
} else if(args.songName.length > 30) {
$('.player .trackInfo .name').css("font-size", "22px");
} else {
$('.player .trackInfo .name').css("font-size", "26px");
}
//歌手名称
$('.player .trackInfo .artist').text(args.artist);
//歌曲图片
if(args.isLocal) {
$('.player .albumPic').css('background', 'url(img/artist.jpg)');
} else {
$('.player .albumPic').css('background', 'url(' + args.albumPic + ')');
}
//获取歌词
getLyric();
}
}
播放音乐代码为:
(主要是做了很多网络加载资源出错或mp3链接失效的处理)
//播放、暂停状态处理
playStatus: function() {
$('.player .controls .play i').attr('class', 'icon-' + (playStatus.playStatus ? 'pause' : 'play'));
if(playStatus.playStatus) {
//networkState = 3则说明音乐mp3URl链接失效,找不到资源,需要重新获取歌曲url链接
//networkState = 0则说明该音乐的mp3Url为空
if($("#audio")[0].networkState != 3 && $("#audio")[0].networkState != 0) {
$("#audio")[0].play();
//如果5秒后还是没有任何资源(reayState=0)而且网络仍然在加载(networkState=2)
//则重新加载刷新歌曲,重新加载次数不超过3次,仍然失败则是网络问题
setTimeout(function() {
console.log("readyState:" + $("#audio")[0].readyState);
console.log("networkState:" + $("#audio")[0].networkState);
console.log("error:" + $("#audio")[0].error);
if($("#audio")[0].readyState == 0 &&
$("#audio")[0].networkState == 2) {
//重新刷新的歌曲信息
playerControls.trackInfo(currentPlaySong);
$("#audio")[0].load();
loadTimes++;
//playStatus.playStatus = false;
//先停顿0.1秒,让程序先加载音乐资源,否则networkState属性会为3,意为找不到资源
if(loadTimes <= 4) {
setTimeout(function() {
playerControls.playStatus();
}, 300);
} else {
loadTimes = 0;
playStatus.playStatus = false;
alert("加载歌曲失败,请检查网络。");
}
} else if($("#audio")[0].networkState == 3) {
//如果5秒后该音乐的网络状态为3,则说明请求资源失效
loadTimes = 0;
invalidTimes++; //歌曲失效次数增加,超过3次刷新url链接
playerControls.trackInfo(currentPlaySong);
$("#audio")[0].load();
//调用播放方法去刷新链接
playerControls.playStatus();
}
}, 5000);
//当点击下一首上一首的次数超过8次,对url进行更新
//因为指定了ajax使用cached,已经加载url的不用重新连接网易云接口
if(playStatus.playTimes >= 8 && currentPlayListIndex != 2) {
init20Songs();
playStatus.playTimes = 0;
}
//显示第一句歌词
if(lyricResult.length <= 0) {
$("#lyric").text("纯音乐,请欣赏。");
} else {
$("#lyric").text(lyricResult[0][1]);
}
} else {
//先判断是否已经获取了3次
if(currentPlaySong.connectTimes < 3) {
//同步获取当前失效歌曲的url链接
if(currentPlayListIndex == 1) {
//如果是网络搜索结果则更新songSearchResults
getMp3Url(songSearchResults, 0, 1, playStatus.currentTrackIndex, false);
songSearchResults[currentPlaySong.index].connectTimes++;
playStatus.playStatus = false;
//重新加载失效的歌曲信息
playerControls.trackInfo(songSearchResults[currentPlaySong.index]);
} else if(currentPlayListIndex == 0) {
//否则更新musicInfos
getMp3Url(musicInfos, 0, 1, playStatus.currentTrackIndex, false);
musicInfos[playStatus.currentTrackIndex].connectTimes++;
playStatus.playStatus = false;
//重新加载失效的歌曲信息
playerControls.trackInfo(musicInfos[playStatus.currentTrackIndex]);
}
$("#audio")[0].load();
//先停顿0.1秒,让程序先加载音乐资源,否则networkState属性会为3,意为找不到资源
setTimeout(function() {
playerControls.playStatus();
}, 300);
alert("歌曲链接失效,请重新点击播放键。");
} else {
playStatus.playStatus = false;
alert("抱歉,该歌曲获取失败,请换下一首歌。");
}
//失效次数超过3次则说明缓存的url链接几乎都失效了,需要重新获取,刷新缓存
invalidTimes++;
if(invalidTimes >= 3 && currentPlayListIndex != 2) {
init20Songs(false, false);
invalidTimes = 0;
}
}
} else {
if($("#audio")[0].played) {
$('#audio')[0].pause();
//恢复歌词字样
if(lyricResult.length <= 0){
$("#lyric").text("纯音乐,请欣赏。");
}else{
$("#lyric").text("歌词");
}
}
}
}
播放进度条和加载动画的代码为:
var timeOut;
//启动定时任务,加载时间进度条和显示加载动画
function timingTask() {
//因为interval会有累积效应,比如alert一个窗口,用户很久都还没点击,
//这时线程因为alert而堵塞,但interval仍然会计算时间,
//将到时间但还没执行的操作添加到队列中。
//当用户点击后,累积的interval就会一次性按顺序执行,
//此时一个clearInterval便无法把所有的interval停止了。
//因此会造成性能问题。
//因此时间间隔比较小的尽量使用内嵌setTimeOut来代替。
//注意:setTimeOut也会累积
playStatus.currentTime = $('#audio')[0].currentTime;
playerControls.playTime();
if(playStatus.currentTime >= playStatus.currentTotalTime) {
$('.player .controls .next').click();
}
//根据网络状态显示加载动画
//readyState == 0 表示have-nothing,还没开始加载资源
//readyState == 2 表示已经有当前数据,但是还没有下面播放的数据
//readyState == 4 表示已经有足够的数据
//networkState == 2 表示请求网络资源正在加载中
//networkState == 1 表示已经请求到网络资源,可以播放了
if(($("#audio")[0].readyState != 4) &&
$("#audio")[0].networkState != 1) {
if($(".loader:first").css("display") == "none") {
$(".loader:first").css("display", "block");
}
} else {
if($(".loader:first").css("display") == "block") {
$(".loader:first").css("display", "none");
}
}
//如果playStatus为true则循环调用自己,
//否则将自己停止
if(playStatus.playStatus) {
timeOut = setTimeout(timingTask, 300);
} else {
clearTimeout(timeOut);
//如果加载动画还在加载,则停止加载
if($(".loader:first").css("display") == "block") {
$(".loader:first").css("display", "none");
}
//刷新播放按钮状态
$('.player .controls .play i').attr('class', 'icon-' + (playStatus.playStatus ? 'pause' : 'play'));
}
}
//使用definedProperty方法监听playStatus属性,当为true的时候才进行定时任务
Object.defineProperty(playStatus, 'playStatus', {
get: function() {
return this._playStatus;
},
set: function(value) {
this._playStatus = value;
if(value == true) {
//开启定时任务
timingTask();
} else {
//按了停止键,则停止定时任务
clearTimeout(timeOut);
//如果加载动画还在加载,则停止加载
if($(".loader:first").css("display") == "block") {
$(".loader:first").css("display", "none");
}
//刷新播放按钮状态
$('.player .controls .play i').attr('class', 'icon-' + (playStatus.playStatus ? 'pause' : 'play'));
}
}
});
播放电脑本地音乐的代码为:
function getLocalFiles() {
//如果本地音乐列表不存在,则进行选择音乐文件
if(localMusicFiles.length <= 0 || $("#localMusicList").css("display") == "block") {
if($("#localMusic").length != 0) {
$("#localMusic").fadeIn(200);
} else {
$("body").append('<div id="localMusic">' +
'<div id="music_top">请选择您的音乐文件:' +
'<div class="music_cross"><span class="icon-cross3"></span></div>' +
'<br /> <span style="font-size:14px">(可以一次性选多首音乐)</span>' +
'</div>' +
'<div id="music_cont"><input id="musicFiles" class="musicInput" type="file" accept="audio/*" multiple/></div>' +
'<div class="music_confirm" id="music_confirm"><span class="icon-checkmark"></span></div>' +
'</div>');
}
$(".icon-cross3").unbind("click").click(function() {
$("#localMusic").fadeOut(200);
});
$("#music_confirm").unbind("click").click(function() {
var musicFiles = document.getElementById("musicFiles");
//value属性保存的是文件的绝对路径,如果为空则说明没有选到文件
if(musicFiles.value) {
$("#localMusic").fadeOut(200);
var files = musicFiles.files;
var hasNotEmpty = true;
var isMp3 = false;
for(var i = 0; i < files.length; i++) {
//如果选择的文件是音频文件才进行添加
if(/audio\/\w+/g.test(files[i].type)) {
//存在音频文件才把上次选择的本地音乐清空,再进行添加
if(hasNotEmpty) {
localMusicFiles = [];
hasNotEmpty = false;
isMp3 = true;
}
var musicInfo = new Object();
musicInfo.songID = -1;
musicInfo.songName = files[i].name;
musicInfo.artist = "本地歌曲";
musicInfo.albumPic = "";
//本地歌曲时长时间还需要获取数据处理
musicInfo.totalTime = 210000;
musicInfo.mp3Url = "";
musicInfo.mp3Url2 = "";
musicInfo.connectTimes = 0;
musicInfo.index = i;
//先将本地音乐文件的files对象保存,等到用户点击歌曲的时候才进行加载播放
musicInfo.musicData = files[i];
//标志该歌曲是本地歌曲
musicInfo.isLocal = true;
localMusicFiles.push(musicInfo);
}
}
//如果选择的所有文件都不是音频文件则进行提醒
if(!isMp3) {
alert("请选择音频文件");
$("#localMusic").fadeIn(0);
} else {
//将本地音乐数据传送到本地音乐列表页面
window.frames[4].postMessage(localMusicFiles, "/");
$("#musicList").css("display", "none");
$("#songList").css("display", "none");
$("#searchMusicList").css("display", "none");
$("#netSearchList").css("display", "none");
$("#localMusicList").css("display", "block");
}
} else {
alert("请选择音乐文件");
}
});
} else {
//如果本地音乐文件列表已经存在,则直接显示出来
listButtonShow(true);
$(".musicList").css("display", "none");
$("#title").css("display", "none");
$("#search").css("display", "block");
$("#local").css("display", "block");
$("#localMusicList").css("display", "block");
}
}
解析歌词的代码为:
//调用trackInfo方法加载音乐信息的时候,也进行加载歌词,并进行解析
function getLyric() {
if(currentPlaySong.isLocal) {
//如果是本地音乐,则把歌词隐藏
$(".lyric").css("display", "none");
} else {
//如果是网络音乐,则把歌词显示
$(".lyric").css("display", "block");
//通过ajax异步获取歌词信息,如果网络异常则给出提示
$.ajax({
url: lyricApi + currentPlaySong.songID,
type: "get",
dataType: 'JSON',
async: true,
cache: true,
success: processLyric,
error: function(xhr, status, error) {
alert("抱歉,获取歌词失败,服务器出故障了。");
}
});
}
}
//解析歌词
function processLyric(lyricData) {
//正则表达式,匹配[00:59.40]这是时间格式
var pattern = /\[\d{2}:\d{2}.\d+\]/g;
//如果标志没有歌词或者歌词内容不匹配时间格式,则归为纯音乐
if(lyricData.nolyric == true || !pattern.test(lyricData.lrc.lyric)) {
lyricResult = [];
lyricArtistMessage = [];
$("#lyricDiv").empty();
$("#lyricDiv").append("<ul><li>纯音乐,请欣赏。</li></ul>")
$("#lyric").text("纯音乐,请欣赏。");
return;
}
var lyricInfo = lyricData.lrc.lyric;
var lines = lyricInfo.split("\n");
//用来存储解析出来的歌词,格式如下[[time,lyricString],[time,lyricString],.....]
lyricResult = [];
lyricArtistMessage = [];
//有一些歌词前面时歌手的介绍,没有歌词的,需要把它去掉
//直到匹配到有时间的歌词才会停止循环
var artistPattern = /\[\w+:/g;
//需要注意,正则表达式使用g模式的话,下一次匹配会从lastIndex开始匹配,
//因为上面使用了pattern进行了匹配,有可能lastIndex不为0,所以需要重置为0
pattern.lastIndex = 0;
while(!pattern.test(lines[0])) {
//先把歌手信息保存下来,因为全屏歌词的时候需要显示
var lyricMessage = lines[0].replace(artistPattern, "").slice(0, -1);
lyricMessage.length > 0 ? lyricArtistMessage.push(lyricMessage) : 0;
//去掉第一个元素并返回剩下元素
lines = lines.splice(1);
}
//lines最后一行是空的话把它去除掉
lines[lines.length - 1].length == 0 && lines.pop();
lines.forEach(function(value, index, array) {
//返回匹配的时间
var time = value.match(pattern);
//将时间置换成空格,返回歌词字符串内容
var lyricString = value.replace(pattern, "");
//由于一行歌词会有多个时间, 如[03:33.65][03:35.39],所以需要进一步分离
time.forEach(function(value2, index2, array2) {
//去掉前后的[]
var t = value2.slice(1, -1).split(":");
//用秒数表示当前歌词的时间
var seconds = parseInt(t[0]) * 60 + parseFloat(t[1]);
//将时间和歌词以数组的形式压进lyriclyricResult中
lyricResult.push([seconds, lyricString]);
});
});
//将结果按照时间排序,保证歌词正确有序输出
lyricResult.sort(function(a, b) {
//如果想要a在b前面,则返回一个负数,否则a想排在b后面,则返回一个正数
//所以想要元素按照升序排序,则返回a与b的差值就行
return a[0] - b[0];
});
addLyric();
//把歌词位置置为初始化
lyricIndex = 1;
showLyric();
}
function addLyric() {
$("#lyricDiv").empty();
var ul = $("<ul></ul>");
//歌手信息
for(var i = 0; i < lyricArtistMessage.length; i++) {
ul.append("<li>" + lyricArtistMessage[i] + "</li>")
}
//歌词信息
for(var i = 0; i < lyricResult.length; i++) {
ul.append("<li>" + lyricResult[i][1] + "</li>")
}
$("#lyricDiv").append(ul);
}
//监听audio的timeUpdate事件,
//第一句歌词会在点击播放按钮时显示出来,
//如果当前时间已经大于第一句歌词的时间,则说明第一句唱完了,
//则把第一句隐藏,更改歌词内容,显示第二句歌词.
//再把当前时间跟第三句歌词时间进行比较,依次循环.
var lyricIndex = 1; //记录当前是第几条歌词
function showLyric() {
//因为歌词头部还有歌手信息,因此高亮的歌词从j开始
var j = lyricArtistMessage.length;
$("#lyricDiv ul li").get(j).style.color = "#ff6666";
//检测第第三句歌词,如果是中文,则歌词大小改为18px
if(lyricResult.length > 3) {
for(var i = 0; i < lyricResult[2][1].length; i++) {
//因为歌词英文的分号为’,所以也要排除这个
if(lyricResult[2][1].charCodeAt(i) > 127 && lyricResult[2][1].charAt(i) != '’') {
$("#lyricDiv ul").css("font-size", "19px");
$("#lyric").css("font-size", "19px");
}
}
}
$("#audio")[0].ontimeupdate = function(e) {
if(lyricResult.length > 0 && this.currentTime > lyricResult[lyricIndex][0]) {
//如果大屏歌词没有打开,则小框进行显示
if($("#lyricDiv").css("display") == "none") {
$("#lyric").fadeOut(0);
$("#lyric").text(lyricResult[lyricIndex][1]);
$("#lyric").fadeIn();
} else {
$("#lyric").text("歌词");
//把上一条歌词颜色进行恢复
$("#lyricDiv ul li").css("color", "#cbc7c7");
//把现在这条歌词高亮显示
$("#lyricDiv ul li").get(lyricIndex + j).style.color = "#ff6666";
//初始歌词的高度是80,所以将80-叠加的高度则得出歌词需要滑动的高度
$("#lyricDiv ul").animate({
"top": 80 - lyricHeight[lyricIndex - 1] + "px"
}, 1000);
}
//把歌词的索引移到下一条
lyricIndex++;
}
}
}
function showFullLyric() {
$(".header button").css("display", "none");
$("#back").css("display", "block");
$(".musicList").css("display", "none");
$(".albumPic").css("display", "none");
$("#lyricDiv").css("display", "block");
//把所有歌词的高度保存,因为设为none之后该值也会丢失
//为了方面后面设置高度,保存的值是叠加高度的值
lyricHeight = [];
for(var i = 0; i < $("#lyricDiv ul li").length; i++) {
var last = i == 0 ? 0 : lyricHeight[i - 1];
lyricHeight.push(last + $("#lyricDiv ul li")[i].offsetHeight);
}
$("#lyric").text("歌词");
//迅速滑到当前播放的歌词
//初始歌词的高度是80,所以将80-叠加的高度则得出歌词需要滑动的高度
if(lyricIndex >= 2){
$("#lyricDiv ul").animate({
"top": 80 - lyricHeight[lyricIndex - 1] + "px"
}, 1000);
}
}
function lyricTip(t, e) {
var lyric = document.getElementsByClassName("lyric")[0];
var tooltipTop = lyric.offsetTop;
if(e.target.id == "lyric") {
var tooltipHtml = "<div id='tooltip' class='tooltip'>点击查看全部歌词 </div>";
$(t).append(tooltipHtml); //添加到页面中
$("#tooltip").css({
"top": tooltipTop + 30 + "px",
"left": "210px",
}).show("fast"); //设置提示框的坐标,并显示
}
}
画星空背景的代码为:
//画星空背景
function drawStars() {
var canvas = document.getElementById('canvas'),
ctx = canvas.getContext('2d'),
w = canvas.width = window.innerWidth,
h = canvas.height = window.innerHeight,
hue = 217, //色调色彩
stars = [], //保存所有星星
count = 0, //用于计算星星
maxStars = 1300; //星星数量
//canvas2是用来创建星星的源图像,即母版,
//根据星星自身属性的大小来设置
var canvas2 = document.createElement('canvas'),
ctx2 = canvas2.getContext('2d');
canvas2.width = 100;
canvas2.height = 100;
//创建径向渐变,从坐标(half,half)半径为0的圆开始,
//到坐标为(half,half)半径为half的圆结束
var half = canvas2.width / 2,
gradient2 = ctx2.createRadialGradient(half, half, 0, half, half, half);
gradient2.addColorStop(0.025, '#CCC');
//hsl是另一种颜色的表示方式,
//h->hue,代表色调色彩,0为red,120为green,240为blue
//s->saturation,代表饱和度,0%-100%
//l->lightness,代表亮度,0%为black,100%位white
gradient2.addColorStop(0.1, 'hsl(' + hue + ', 61%, 33%)');
gradient2.addColorStop(0.25, 'hsl(' + hue + ', 64%, 6%)');
gradient2.addColorStop(1, 'transparent');
ctx2.fillStyle = gradient2;
ctx2.beginPath();
ctx2.arc(half, half, half, 0, Math.PI * 2);
ctx2.fill();
// End cache
function random(min, max) {
if(arguments.length < 2) {
max = min;
min = 0;
}
if(min > max) {
var hold = max;
max = min;
min = hold;
}
//返回min和max之间的一个随机值
return Math.floor(Math.random() * (max - min + 1)) + min;
}
function maxOrbit(x, y) {
var max = Math.max(x, y),
diameter = Math.round(Math.sqrt(max * max + max * max));
//星星移动范围,值越大范围越小,
return diameter / 2;
}
var Star = function() {
//星星移动的半径
this.orbitRadius = random(maxOrbit(w, h));
//星星大小,半径越小,星星也越小,即外面的星星会比较大
this.radius = random(60, this.orbitRadius) / 8;
//所有星星都是以屏幕的中心为圆心
this.orbitX = w / 2;
this.orbitY = h / 2;
//星星在旋转圆圈位置的角度,每次增加speed值的角度
//利用正弦余弦算出真正的x、y位置
this.timePassed = random(0, maxStars);
//星星移动速度
this.speed = random(this.orbitRadius) / 50000;
//星星图像的透明度
this.alpha = random(2, 10) / 10;
count++;
stars[count] = this;
}
Star.prototype.draw = function() {
//星星围绕在以屏幕中心为圆心,半径为orbitRadius的圆旋转
var x = Math.sin(this.timePassed) * this.orbitRadius + this.orbitX,
y = Math.cos(this.timePassed) * this.orbitRadius + this.orbitY,
twinkle = random(10);
//星星每次移动会有1/10的几率变亮或者变暗
if(twinkle === 1 && this.alpha > 0) {
//透明度降低,变暗
this.alpha -= 0.05;
} else if(twinkle === 2 && this.alpha < 1) {
//透明度升高,变亮
this.alpha += 0.05;
}
ctx.globalAlpha = this.alpha;
//使用canvas2作为源图像来创建星星,
//位置在x - this.radius / 2, y - this.radius / 2
//大小为 this.radius
ctx.drawImage(canvas2, x - this.radius / 2, y - this.radius / 2, this.radius, this.radius);
//没旋转一次,角度就会增加
this.timePassed += this.speed;
}
//初始化所有星星
for(var i = 0; i < maxStars; i++) {
new Star();
}
function animation() {
//以新图像覆盖已有图像的方式进行绘制背景颜色
ctx.globalCompositeOperation = 'source-over';
ctx.globalAlpha = 0.5; //尾巴
ctx.fillStyle = 'hsla(' + hue + ', 64%, 6%, 2)';
ctx.fillRect(0, 0, w, h)
//源图像和目标图像同时显示,重叠部分叠加颜色效果
ctx.globalCompositeOperation = 'lighter';
for(var i = 1, l = stars.length; i < l; i++) {
stars[i].draw();
};
//调用该方法执行动画,并且在重绘的时候调用指定的函数来更新动画
//回调的次数通常是每秒60次
window.requestAnimationFrame(animation);
}
animation();
}
- 还有音乐列表歌名搜索、网络音乐搜索、自定义div弹出框、列表信息显示、iframe跨域传送信息、播放快捷键绑定等功能,在index.js代码文件中都有很详细的注释,可以自行查看。
后序
- 其实在写播放器的时候,是没有一个整体的框架的,基本都是想到什么功能,就添加一个函数,几乎所有脚本代码都写在一个index.js文件中了。这也是后续学习的方向,毕竟软件性能、代码维护都需要良好的框架支撑。
- 第一次写GitHub,陈述不清楚,还望见谅,继续学习中…
- Stay Hungry,Stay Foolish. Continue Hello World!