模擬網易雲的H5音樂播放器
阿新 • • 發佈:2018-12-19
轉發我在github釋出的一個H5音樂播放器
前言
- 這是我第一個GitHub專案,之前一直想在GitHub寫點東西,近期又在學前端,剛好學到audio標籤,平常時也比較喜歡聽音樂寫程式碼,因此就萌生了自己寫一個音樂播放器的想法。利用了下班和週末的空閒時間,用了兩週時間終於寫出來了。當用程式碼一個個地把自己的想法實現出來,那種興奮和成就感是無與倫比的。甚至週末的時候一碼就碼到通宵,還不覺得累哈哈。因為我也是初學,用GitHub把自己的註釋和思路寫出來,可以提供給跟我一樣的夥伴來練手。
- 首先先要感謝兩位大神的知識分享:
- CeuiLiSA:GitHub首頁
該音樂播放器是基於王樂平的基礎上修改的,借鑑了他的思路進行完善和新增功能。該部落格的連結為
- CeuiLiSA:GitHub首頁
該音樂播放器是基於王樂平的基礎上修改的,借鑑了他的思路進行完善和新增功能。該部落格的連結為
音樂播放器效果
-
我把音樂播放器放到了騰訊雲伺服器了,可以直接點選下面連結檢視效果,音樂播放器具備的功能基本都實現了。如當前歌單音樂列表、輸入網易雲使用者名稱獲取歌單進行切換、音樂列表歌名搜尋、網路音樂搜尋、電腦本地音樂新增播放、歌詞滾動顯示、歌曲播放進度等。
-
音樂播放器的整個介面效果:
-
從上到下的佈局分為“音樂列表”“您的歌單”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!