[項目] 網易雲音樂項目總結


  最近准備把之前做的一個仿網易雲音樂的自制音樂網頁播放器項目做一個總結。

  相關功能如下:

  1.     通過后台頁面上傳歌曲、編輯歌曲功能。
  2.     前端頁面自動更新播放熱度高的歌曲
  3.     在線聽歌、查看歌詞。且配有相應的播放動畫。

 

  預覽鏈接:https://leonardo-zyh.github.io/163-music-demo/src/index.html

  可通過微信二維碼打開:

  

   該項目主要是使用了jQuery以及MVC模塊化的思想來完成的移動端音樂會播放器,因此在介紹這個應用的制作思路和流程之前,我想重新總結一下對模塊化和MVC的理解。

 


 

 

模塊化

我的認識中模塊化是通過MVC的V,也就是View來划分的,把頁面中看得見的區域進行功能划分,每一個功能不同的區域就是一個分開的模塊,

模塊之間是通過命名空間或者說事件中心eventHub來進行聯系的,這種聯系方式的好處就是可以任意地跨模塊進行信息的交流,頁面中的任意模塊都能與另一任意模塊進行交流,只要它們綁定同樣的事件就可以了。但缺點也很明顯,就是事件中心是全局環境下的事件中心,如果一個事件觸發了兩個模塊來發布相同的事件,那就會不可避免地產生沖突,這個時候只能通過改變其中一個模塊的發布事件的事件名來消除這種沖突,這顯然並不是一種理想的解決辦法,因為本質上他們就是相同的事件,給相同的事件不同的命名的做法並不恰當,因此這也是我認為的MVC模塊交流方式的缺點。
 

 
以下是eventHub的代碼:
window.eventHub={ events:{}, emit(eventName,data){ for(let key in this.events){ if (key===eventName){ let fnList=this.events[key] fnList.map((fn)=>{ fn.call(undefined,data) }) } } }, on(eventName,fn){ if (this.events[eventName]===undefined){ this.events[eventName]=[] } this.events[eventName].push(fn) } }

 


 

MVC模式

  MVC模式(Model-View-Controller)是軟件工程中的一種軟件架構模式,把軟件系統分為三個基本部分:
  模型(Model)、視圖(View)和控制器(Controller)

  • 模型(Model)
    模型層:數據保存,可以簡單理解就是數據層,用於提供數據。在項目中,(簡單理解)一般把數據訪問和操作,比如將對象關系映射這樣的代碼作為Model層,也就是對數據庫的操作這一些列的代碼作為Model層。比如代碼中我們會寫DAO和DTO類型的代碼,那這個DAO和DTO我們可以理解為是屬於Model層的代碼。
  • 視圖(View)
    視圖層:用戶界面,就是UI界面,用於跟用戶進行交互。一般所有的JSP、Html等頁面就是View層。
  • 控制器(Controller)
    控制層:業務邏輯,Controller層的功能就是將Model和View層進行關聯。比如View主要是顯示數據的,但是數據又需要Model去訪問,這樣的話,View會先告訴Controller,然后Controller再告訴Model,Model請求完數據之后,再告訴View。這樣View就可以顯示數據了
   
   View就是當前模塊所代表的看得見的功能划分部分,View一般需要你指定這個部分在HTML對應的元素的ID,例如我把一個歌曲列表當作一個模塊,那么一般這個列表會在HTML以一個ul標簽的形式表示,那么我們在使用MVC時,就要給View指定這個ul標簽的ID作為這個MVC模塊所要操控的區域的ID。一般在對象View里還有屬性template來表示這個功能區域的HTML模版,然后通過View對象里面你設置的函數去改變這個template,例如增減標簽的類名,例如把之后Model對象里面的數據data內容放入標簽中等等,然后通過render()函數來更新功能區的HTML代碼,使頁面視圖發生相對應的改變。總的來說,View要做的事情,就是改變網頁的用戶視覺,去把代碼改變的內容以直觀的方式呈現在網頁中,需要切記的是每個MCV模塊中View所代表的區域之間是不能互相交叉的,因此在進行模塊化時要明確好每個模塊的職責。
一個簡單的View對象的代碼例子:
let view={ el:'#用戶界面', template:` <div>HTML模版</div>
 `, init() { this.$el = $(this.el) }, render(){ $(this.el).html(this.template) } }

 

 

   
Model所代表的是當前模塊所包含的數據以及操作數據的方法。以歌單模塊為例,那么歌單中的每個歌曲的ID、以及ID對應的歌曲名字和歌手就是我們所需要數據data,這些數據會存放在這個歌單模塊的Model中,每當模塊需要存儲、獲取或者更新數據,都要呼叫Model模塊並由Model模塊來執行這些操作。需要記住,每個模塊的Model並不需要儲存整個應用的所有數據,而是只需儲存這個模塊所對應的必須要的數據即可,例如歌單模塊因為需要讓用戶知道每一首歌是什么,因此需要歌名以及歌手的數據。而因為要和其他模塊進行交流,例如讓其他模塊知道用戶是否點擊了某首歌,因此還需要儲存歌曲的ID,以方便之后歌單模塊通過事件中心把點擊的歌曲的ID數據傳遞給其他的模塊。歌單模塊並不需要存儲歌詞、歌曲封面這些數據,是因為這個模塊並不展示和操作這些數據,而其他模塊可以通過歌單模塊傳來的ID去獲取這些數據。這篇文章介紹的音樂播放器的項目就是Model配合LeanCloud數據庫和七牛數據庫實現的。(前者作為數據庫引用,后者作為音樂數據存放)
一個簡單的Model對象的代碼例子:
let model={ data:{}, fetch(){......}, save(){......}, update(){......} }

 

 

  Controller作為業務邏輯,Controller層的功能就是將Model和View層進行關聯。Controller代表的是控制當前模塊在不同的時刻所進行的操作,比如,Controller對象里一般都會有init方法、bindEvents方法和bindEventHub方法。init方法意思就是在模塊初始化的時候,需要做些什么,因此我們會在init方法里面初始化view、初始化model,進行事件綁定,進行事件發布訂閱中心的事件訂閱等等,這些就是我們在模塊初始化時要做的事情。然后在元素觸發事件時模塊需要做什么,在其他模塊發布事件后模塊需要做什么,都分別反映在了Controller的bindEvents方法和bindEventHub方法中,因此Controller就像一個控制塔,有條不紊地在合適的時候處理着合適的事情,是統籌Model和View的中心。

一個簡單的Controller對象的代碼例子:
let controller={ init(view,model){ this.view=view this.model=model this.bindEvents() this.bindEventHub() }, bindEvents(){......}, bindEventHub(){......} }

    
  總的來說,MVC就是一種代碼的組織思想,View代表功能區視圖管理着與直觀內容有關的變化,Model則作為數據中心管理着該視圖的所有數據,Controller則作為控制中心管控着View和Model的運作時機和運作方式。
  相信在你看完以上我對模塊化和MVC的項目總結之后,會幫助你更好地梳理接下來我要介紹的音樂播放器的思路。因為這個應用涉及的代碼很多,所以我只能介紹重要的思路,以及會在最后說一下在制作過程中遇到的幾個坑、問題以及解決這個問題的思路的做法

 

項目的制作思路

    當你有了制作某個項目的想法,你第一件要做的事情是什么,不是直接寫代碼,而是分析這個項目,我們可以通過以下三個圖例來進行分析:

    1.用例圖(use cases)

    分析當你身為用戶或者應用管理員的時候,使用應用的時候需要什么的頁面,頁面需要怎么樣的功能,這就是用例圖會表達出來的內容。

    例如音樂播放器這個應用,身為普通用戶的話,我們可以查看首頁、查看歌單頁和歌曲頁,歌曲頁里面可以聽歌、可以暫停以及可以查看歌詞,我們還可以搜歌,可以搜歌來搜出歌手和歌曲名等等。

    通過這些分析,你就會了解到你當前要制作的這個應用,他需要怎么樣的功能以及每個功能應該出現在哪一個頁面當中。

 2.線框圖(也叫草圖,stretch)

    線框圖要展示的,就是你要制作的應用中,每個頁面功能區的布局,也就是線框圖會告訴你這個應用含有多少個頁面,每個頁面里有着哪些功能區,以及功能區的大體位置也能在上面體現出來。

 3.系統架構圖

    系統架構圖展示的是在該應用中,前端頁面、后端頁面以及數據庫中使用的是什么工具,例如音樂播放器中,前端頁面使用的是jQuery,后端頁面使用的是LeanCloud提供的API,數據庫使用的是    LeanCloud和七牛,以及這三者之前的交互方式,例如前端頁面和后端頁面的交互方式是通過AJAX來進行的。

  
  在進行完了以上的分析,我們對這個音樂播放器的應用就有了大概的認識,作為普通用戶和管理員兩種不同的角色,我們應當設計兩個頁面,管理頁面供管理員去管理音樂播放器中的音樂信息,管理頁面應當提供上傳歌曲、編輯歌曲以及刪除歌曲的功能,每首歌曲管理員應當有權限去設置歌曲的歌名、歌手、封面、歌曲鏈接以及歌詞信息,這樣,管理員就能通過這個頁面去管理用戶頁面中展示的歌曲了。管理頁面具體要如何實現,我在這里就不具體敘述了,只需你熟練掌握MVC的基本操作,然后能夠讀懂七牛文檔以及LeanCloud文檔后使用相關的API,就可以輕松地把這個后台管理頁面做出來了。
這里給大家展示一下一個后台管理系統以及它的模塊化划分:
 
 
 
  用戶頁面應該有三個:音樂播放器首頁、歌單頁、歌曲播放頁面,但這次項目因為時間沖忙,因此沒有制作歌單頁,所以我們把關注點放在首頁和歌曲播放頁就好,首先應該首頁應該有如下功能:歌曲名和歌手名字的展示,歌單名和封面的展示,這些內容全都通過LeanCloud的API來獲取即可,因此也不詳細地去說了。
  除了首頁,還有一個歌曲播放頁,這個頁面的話設計到歌曲的封面,歌詞,歌名。我來考讀者一個問題,這個歌曲播放頁如何才能獲取到這些數據和信息呢?如果你想的是在LeanCloud數據庫里遍歷來查找那就不對了,正確的做法應該是,用戶在首頁中點擊了想聽的歌曲,之后調整到歌曲播放頁面,把該頁面的url后面的查詢參數設置成你點擊的歌曲的ID,那么我們在播放歌曲頁面中只需要通過捕獲url上面的這個查詢參數,即可獲得該歌曲的ID,之后再用這個ID在LeanCloud上獲取對應的數據展示在頁面即可,這樣歌曲的封面、歌詞、歌名以及歌手等全部信息我們都能獲得得到。
  關於音樂播放器的樣式問題,這是需要自己去花時間去尋找優秀的設計模板,並進寫模仿、修改和編寫才能得到的內容,因此就不在這里進行闡述了。
 
  主頁面做呈現的效果:
 
   音樂播放器首頁

   歌曲播放頁面
 
 
 

 

 

關於項目中所遇到的問題

   1.由於使用了移動端不支持的ES6語法導致的BUG
    在進行音樂作品播放器的制作過程中,我在一些地方使用了ES6的新語法展開語法 ...,這個語法的表示的是當前對象的所有屬性,如:

 

let attributes={name:"xzb",age:18} let obj={id:1,...attributes} console.log(obj) // {id:1,name:"xzb",age:18}

 

    這個語法在是【使用中很方便,但是當你在移動端使用它的時候,很容易出現語法錯誤,移動端不支持
    后面只能使用Object.assign方法來代替它了。

    解決辦法:使用Object.assign()


    2.無法對移動端進行調試
    第二個坑是由第一個坑衍生出來的,在發現第一個坑之后,我的第一個反應是,十分無奈,為什么這么說呢?哥,你在PC端出錯,我還能通過控制台來看看出錯的地方在哪,你在移動端出錯,我.....
    好吧,只能靠萬能的互聯網了,在一番資料的查詢之后,我得到了想要的解決辦法,有以下四個:
    (1)通過alert()來進行檢驗
    雖然說移動端沒有控制台,但移動端還是可以alert的吖,因此我們只要在我們認為出錯的地方的前后進行alert(),若發現前面的alert運行了,后面的alert沒有運行,那么恭喜你,你的猜測是正確的,出錯的地方就是此處。通過這種辦法我們就可以在移動端知道自己出錯的地方了。
    (2)通過全局的onerror來進行檢驗
    通過第一種方法我們的確可以在不斷的嘗試下知道出錯的地方,但是這樣效率太低下了,於是我們有第二種辦法,通過監聽全局的error來顯示錯誤,以及顯示錯誤的出處。代碼如下:

<script> window.onerror=function(message,file,row){ alert(message,file,row) } </script>

 

    onerror接受四個參數,第一個是出錯信息,第二個出錯文件,第三個是出錯的行數,第四個是出錯的列數,由於列數在此處對我沒什么太大的作用,因此在上述代碼中我把它省略了。
    (3)自己手寫一個console函數法
    自己在頁面上寫一塊console的區域,然后把console的值直接顯示在區域內即可,具體代碼如下:

<div id="consoleOutput" style="......"></div>
<script> window.console={ log(x){ let p=document.createElement('p') p.innerText=x consoleOutput.appendChild('p') } } </script>

 

    (4)直接引入騰訊制作的vConsole庫
    直接引入騰訊制作的vConsole庫,就可以在移動端擁有一個console了,但是需要記住在調試完之后記得刪掉這些調試工具,以免出現在用戶使用的頁面中。

  注意:http-server局域網調試,建議關閉防火牆

    3.由於IOS的移動端不支持animation-play-state語句而導致的BUG。(注:IOS12已修復)
    在我對移動端中的所有報錯都進行了修復之后,我在歌曲播放頁面又發現了一個新的BUG,那就是在點擊光盤進行播放的時候,光盤沒有如我代碼所寫那樣進行播放,百思不得其解之下,我發現網上有許多人和我出現了類似的BUG,於是乎我就去尋找出現這種現象的原因,以及解決的辦法,最后我發現原來是IOS不支持animation-play-state語句而導致的,而在安卓的移動端上則不會出現這樣的BUG。
    如何是好,沒有了animation-play-state,我就無法去控制CSS3動畫的播放和暫停,如果單純的用animation:none;來控制,就會出現動畫每次都重頭開始進行的錯誤效果,后來我也嘗試了animation-fill-mode: forwards;來嘗試讓動畫每次停在最后的狀態,但由於歌曲的暫停是隨機的,而不是由動畫是否播放完畢來決定是否暫停的,因此這個方法也是行不通的,怎么辦好。我不斷的嘗試,不斷地搜索資料,最后我看到了一個網上的解決方案:給光盤所在元素添加一個父元素,當每次點擊光盤暫停歌曲播放時,用父元素記錄一下光盤每次暫停時transform的值,並讓父元素的transform也等於這個值,若transform本來就有值,那就在transform后面更新這個值,就可以完成歌曲暫停,光盤動畫暫停,歌曲繼續開始,光盤動畫繼續開始的效果
    具體的實現代碼如下:

recordTransform(){ let coverTransform=this.view.$el.find('.coverWrapper').css('transform') let coverWrapperTransform=this.view.$el.find('.coverWrapperParent').css('transform') let transformDeg=coverWrapperTransform==='none'?coverTransform:coverTransform.concat(' ',coverWrapperTransform) this.view.$el.find('.coverWrapperParent').css('transform',transformDeg) }

 



    4.由於IOS微信不支持webp格式圖片所引起的BUG
    由於我的音樂播放器中的歌單用的是webp文件的封面圖片,這個格式的圖片是谷歌開發的一種旨在加快圖片加載速度的圖片格式。WebP 的優勢體現在它具有更優的圖像數據壓縮算法,能帶來更小的圖片體積,而且擁有肉眼識別無差異的圖像質量;同時具備了無損和有損的壓縮模式、Alpha 透明以及動畫的特性,在 JPEG 和 PNG 上的轉化效果都相當優秀、穩定和統一。
    可惜IOS上的微信就是不支持這種格式的圖片,因此沒有辦法,我最后解決這個BUG的方案就是把webp圖片全部轉換成了JPG然后重新上傳至應用中。
    解決辦法:更換應用圖片格式


     終於算是介紹完了如何仿照網易雲音樂自制一個音樂網頁播放器,當我做完這個應用的時候,我覺得自己對MVC的理解和使用都更為熟練了,接下來我會完成Vue相關的應用,並繼續給大家帶來完成后的心得和感想,希望能對你們有所幫助~

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM