[项目] 网易云音乐项目总结

  最近准备把之前做的一个仿网易云音乐的自制音乐网页播放器项目做一个总结。

  相关功能如下:

  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相关的应用,并继续给大家带来完成后的心得和感想,希望能对你们有所帮助~

 

posted @ 2018-11-29 21:56  Leoz/  阅读(5442)  评论(0编辑  收藏  举报