天天看點

如何快速做一個HTML5移動播放器

這段時間公司一直在做一個PC的教育類單頁應用,龐大複雜,涉及非常多H5的知識,音頻就是其中的一部分。前些天偷台風的閑暇時寫了一個移動音樂播放器,作為練手項目(存放在碼雲)。若你覺得該文章對你有幫助,别忘了點個贊啦。

**線上位址:**請猛擊這裡

**源碼:**請猛擊這裡

注意:使用PC浏覽最好打開移動裝置模式,使用移動裝置浏覽需要關閉無痕浏覽模式(否則無法使用本地存儲,一般浏覽器都是預設不開啟),項目需要在本地伺服器或線上伺服器運作,以file:///形式的位址打開是無法進行ajax請求的,進而無法看到音樂資料。

如何快速做一個HTML5移動播放器

項目實作的功能及所用知識

  • 播放器的基礎操作,上一首,下一首(順序播放、随機播放、單曲循環),播放暫停,滑動時間軸的歌詞定位
  • 初始handlebar模闆渲染音樂清單資料,下拉滾動加載音樂清單資料。
  • 歌曲清單可添加喜愛音樂,于下次重新整理時更新喜愛音樂清單,基于HTML5本地存儲。
  • 布局采用rem布局,自适應移動端手機裝置。
  • iconfont線上圖示應用的使用

項目目錄檔案結構

**css:**存放樣式檔案

**lib:**存放公共腳本庫

**js:**存放項目腳本檔案

**img:**存放圖檔

**fonts:**項目字型檔案

**res:**項目音樂資源

**ui:**項目ui檔案(psd)

// ============================配置變量================================
var rootPath = window.location.href.replace(/\/\w+\.\w+/, "/");
var Settings = {
playmode: 0, //0清單循環,1随機,2為單曲循環
volume: 0.5, //音量
initNum: 10, //清單初始化歌曲數
reqNum: 10 //後續請求歌曲數
};

// ============================工具函數================================
var Util = (function() {
return {

}
})()
// ============================Dom選擇器================================
var Dom = {

}

// ============================全局變量================================
var winH = $(window).height();

var songNum = 0; //目前清單歌曲數目
var lrcHighIndex = 0; // 歌詞高亮索引
var lrcMoveIndex = 0; // 歌詞移動機關索引
var moveDis = 0; // 單句歌詞每次移動距離

var duration = 0; // 目前歌曲的時間
var index = 0; //目前播放歌曲的索引
var songInfo = null; // 目前歌曲資訊
var songModelUI = null; // 目前歌曲UI模型
var timeArr = []; //目前歌曲時間數組
var formatTimeArr = []; //目前歌曲時間數組(格式化為秒數)

// ============================入口函數================================
function main() {
initUIFrame();
var initModel = PlayerModel();

var songListUI = ModelUIFrame(Dom.songListContainer);
var lsongListUI = ModelUIFrame(Dom.lSongListContainer);
initModel.getSongList("data/data.json", function(data) {
// 生成所有歌曲清單
songListUI.renderList(data, 0, null, function() {
songListUI.updateList();
});
// 生成喜愛歌曲清單
initModel.getLoveSongArr(function(lSongArr) {
lsongListUI.renderList(data, 1, lSongArr);
});
// 添加動畫
Util.addAnimationDelay(Dom.song);
// 儲存歌詞資料
initModel.saveLyric(data);

});
EventHandler();
}
// ============================初始化UI函數================================
function initUIFrame() {

}
// ============================實作資料互動方法================================
function PlayerModel() {

}
// ============================模型動态UI子產品================================
function ModelUIFrame(container) {

}
// ============================事件綁定子產品================================
function EventHandler() {

}
// 調用入口函數
main();
           

功能點詳解

Handlebar.js初次渲染及滾動加載

使用前端模闆優點是把資料和結構分離出來,代碼更清晰。但後來發現handlerbar.js似乎無法在js中示例模闆對象,而html中的handlebar在初次進入頁面便會被編譯了,是以後續添加音樂還是采用傳統的拼接字元串的方式,如果你有更優雅的動态加載方式,歡迎讨論交流。

**html:**handlebars模闆包含在script标簽之中并且type類型為”text/x-handlebars-template”,在初始化頁面的時候根據js擷取資料植入後就渲染出相應的html。

<script id="sListTpl" type="text/x-handlebars-template">
{{#each this}}
{{#isInitData this @index}}
<li class="song btm-line" data-src={{songSrc}} data-index={{id}}>
	<div class="poster">
		<img src={{poster.thumbnail}}>
	</div>
	<div class="songinfo">
		<h2 class="lsongname">{{songName}}</h2>
		<sub class="lsinger">{{singer}}</sub>
	</div>
	<div class="loveflag">
		<i class="icon icon-love {{#if loveFlag}}active{{/if}}"></i>
	</div>
</li>
{{/isInitData}}
{{/each}}
</script>
           

js:

function renderAllList(data) {
    var preTpl;
    var lsongArr = Util.getItem('lsonglist') === null ? [] : JSON.parse(Util.getItem('lsonglist'));
    // 生成清單
    if (!sListTpl) {
        // 後續動态生成歌曲
        var tpl = "";
        var songIndex = songNum;
        $.each(data, function(index, el) {
            if (index >= songIndex && index < songIndex + Settings.reqNum) {
                tpl += "<li class='song btm-line' src='res/music/" + songNum + ".mp3' data-index='" + songNum + "'><div class='poster'>[站外圖檔上傳中……(1)]</div><div class='songinfo'><h2 class='lsongname'>" + el.songName + "</h2><sub class='lsinger'>" + el.singer + "</sub></div><div class='loveflag'><i class='icon icon-love '></i></div></li>";
                songNum++;
            }
        });
        $(container).append($(tpl));
    } else {
        // 首次生成歌曲
        preTpl = Handlebars.compile(sListTpl);
        $(container).html(preTpl(data));
    }
    // 更新喜愛圖示
    if (lsongArr.length !== 0) {
        $.each(lsongArr, function(index, val) {
            Dom.songListContainer.find(".song").eq(val).find(".icon-love").addClass('active');
        });
    }
}
           

rem布局自适應方案

大體上指的是html根元素上定義一個字型大小,然後css樣式定義時使用rem作為機關,包括margin、paddding、用于絕對定位的機關等等。然後js根據手機裝置的螢幕大小,改變根字型的大小,這樣整個頁面也會跟着相應的縮小或放大。

更多詳解,請看這一篇文章《移動端自适應布局解決方案——rem》,您可以猛擊這裡跳轉。

關于歌詞的同步方案實作

目前音樂播放器的歌詞同步顯示大概有兩種,一種是精确到單個文字,一種是精确到單行歌詞。本文實作的是第二種。

整體實作思路

頁面初始化時,請求歌曲資料json(本地json檔案模拟),其中歌名、歌手、圖檔等按需渲染到html中,将歌詞存儲到localStorage中。此時,F12打開chrome調試器,進入Application-LocalStorage可以看到:

如何快速做一個HTML5移動播放器

點選一首歌進入播放頁面後,歌詞就會從本地存儲中讀取,此時你會看到生成這樣的歌詞結構:

如何快速做一個HTML5移動播放器

每一行歌詞都将要将歌詞時間綁定在data-point上,監聽歌曲播放的timeupdate事件,當歌曲的時間(經過取整處理)與目前data-point值相等時,就為目前歌詞高亮(相當于給p添加current類名),并且根據目前高亮歌詞的index索引将整個歌詞盒子向上移動p标簽的高度+margin-top的高度。

lrc歌詞的結構

來自網易雲音樂的歌詞資料:

[00:14.64]如果不是那鏡子\n[00:16.73]不像你不藏秘密\n[00:21.26]我還不肯相信\n[00:23.02]沒有你我的笑更美麗\n[00:28.99]那天聽你在電話裡略帶抱歉的關心\n

[00:16.959]摘一顆蘋果\n[00:19.800]等你從門前經過\n[00:22.700]送到你的手中幫你解渴\n[00:25.570]像夏天的可樂\n

[00:00.00] 作曲 : 周傑倫\n[00:01.00] 作詞 : 周傑倫\n[00:05.620]\n[00:37.980]親吻你的手\n
           

可以看到格式 = [時間點] + 要顯示的文字 + \n

這裡有兩個坑需要注意:

有的歌詞秒數是精确到小數點後兩位,有的是三位。

有的歌詞(周傑倫《算什麼男人》)格式是**[時間點]+\n**

時間歌詞建立映射

首先以\n将歌詞字元串分割成以**[時間點]文字的數組,但由于這樣分割之後最後一個元素是空的,是以用tempArr.splice(-1, 1)**删除最後一個元素。

接下來循環周遊這個臨時數組,由于上面提到的秒數精确度的問題,是以判斷一下index為9是否為數字,若為數字則将該位數字删除。(采用字元串截取方式,若你對js字元串方法不熟悉,可以猛擊這裡)

經過這樣的處理之後,臨時數組的元素格式不再有差別了,此時再進行字元串截取,将截取到的時間點放入timeArr,将截取的歌詞放入lyricArr,并以傳回儲存着這兩個變量的對象。

function createArrMap(lyric) {
	var timeArr = [],
	    lyricArr = [];
	var tempArr = lyric.split("\n");
	tempArr.splice(-1, 1);
	var tempStr = "";
	$(tempArr).each(function(index) {
	    tempStr = this;
	    if (tempStr.charAt(9).match(/\d/) !== null) {
	        tempStr = tempStr.substring(0, 9) + tempStr.substring(10);
	    }
	    timeArr.push(tempStr.substring(0, 10));
	    lyricArr.push(tempStr.substring(10));
	});
	return {
	    timeArr: timeArr,
	    lyricArr: lyricArr
	};
}
           

生成歌詞

由于上面歌詞格式造成時間點對應的歌詞為空,此時如果渲染出一個

标簽的高度将為0,這會影響歌詞向上移動距離的不統一。是以下面作出個判斷如果為空,則替換為“————–”。(為空的時候大多數是歌曲中間停頓或過渡的時候)

function renderLyric(songinfo) {
    var arrMap = Util.createArrMap(songinfo.lyric);
    var tpl = "";
    $.each(arrMap.lyricArr, function(index, lyric) {
        var lyricContent = lyric === "" ? "--------------" : lyric;
        tpl += "<p class='' data-point='" + arrMap.timeArr[index] + "'>" + lyricContent + "</p>";
    });
    Dom.lrcwrap.html(tpl);
}
           

歌詞同步

歌詞同步我寫在了syncLyric方法中,監聽audio元素的timeupdate事件調用。

這個方法接收兩個參數,第一個是目前播放歌曲時間(秒),第二個是轉化為秒數的時間點數組。

如果目前時間>=時間點,那麼高亮目前歌詞(以lrcHighIndex)存儲,并且lrcHighIndex自增1。

當歌詞高亮索引lrcHighIndex>=1即歌詞高亮不為第一句時,計算索引并讓歌詞盒子向上移動。

function syncLyric(curS, formatTimeArr) {
    if (Math.floor(curS) >= formatTimeArr[lrcHighIndex]) {
        Dom.lrc.eq(lrcHighIndex).addClass('current').siblings().removeClass('current');
        if (lrcHighIndex >= 1) {
            lrcMoveIndex = lrcHighIndex - 2;
            moveDis += Util.getMoveDis(lrcMoveIndex);
            Dom.lrcwrap.animate({
                "top": "-" + moveDis + "px"
            }, 100);
            lrcMoveIndex++;
        }
        lrcHighIndex++;
    }
}
           

PS:更多前端資訊、技術幹貨,請關注公衆号「前端新視界」,背景回複 “1” 擷取100本PDF前端電子書

回複 “2” 擷取小編精選的Python程式設計電子書

如何快速做一個HTML5移動播放器

繼續閱讀