这个播放器的开辟用时2个多月,并非说它有多庞杂,相反它的功用还异常不完美,仅具雏形。之所以磨磨蹭蹭这么久,一是因为迁延,二也是练习公司项目太紧。8月尾完毕练习前写完了款式,以后在家余暇时刻多了,集中精力就把JS部份做完了。
这个播放器确切比当初设想的庞杂,最先只盘算做一个搜歌播放的功用。如今做出来的这个播放器,可以猎取热点歌曲,可以搜歌,可以调解播放进度条,功用确切完美不少。
此次完成这个项目也是收成颇丰,点了不少新的妙技点,固然,这个大略的小项目也挖了不少坑,不知道啥时刻能填上……
话不多说,看代码吧。
Muse-ui
不记得在哪一个网站看到这个组件库的了,以为好酷炫,因而用起来~
这是官网:地点
运用这个组件库的缘由除了美丽,还因为这是基于Vue 2.0,无缝对接,轻易。
运用要领跟之前的插件一样,npm装置:
npm install --save muse-ui
装置好后,在main.js
中注册。
import MuseUi from 'muse-ui'
import 'muse-ui/dist/muse-ui.css'
import 'muse-ui/dist/theme-light.css'
Vue.use(MuseUi)
就可以在项目中运用了。
PS:Muse-ui的icon是基于谷歌的Material icons,人人可以依据本身的需求到官网找icon的代码。
组件构造
接着我们就该搭建这个播放器的组件了。
构造以下:
||-- player.vue // 主页面
| |-- playerBox.vue // 播放器组件
| |-- popular.vue // 热点歌曲页面
| |-- songList.vue // 歌曲列表页面
| |-- play.vue // 播放器页面
| |-- search.vue // 搜刮页面
PS:热点歌曲、搜刮页面都能进入歌曲列表页面,播放器组件playerBox.vue
是放<audio>
标签的组件,是功用性组件。
我们来离别叙说:
1.player.vue
直接看代码吧:
<template>
<div class="player">
<!-- banner here-->
<router-view></router-view>
<!-- navbar here -->
<mu-paper>
<mu-bottom-nav :value="bottomNav" @change="handleChange">
<mu-bottom-nav-item value="popular" title="盛行" icon="music_note" to="/popular"/>
<mu-bottom-nav-item value="play" title="播放" icon="play_arrow" to="/play"/>
<mu-bottom-nav-item value="search" title="搜刮" icon="search" to="/search"/>
</mu-bottom-nav>
</mu-paper>
<!-- html5 player here -->
<playerBox></playerBox>
</div>
</template>
<script>
import playerBox from './playerBox.vue'
export default {
name: 'player',
data(){
const pa=this.$route.path;
const Pa=pa.slice(1);
return{
bottomNav: Pa
}
},
components: {
playerBox
},
methods:{
handleChange (val) {
this.bottomNav = val
},
changebar(){
const va=this.$route.path;
const Va=va.slice(1);
this.bottomNav = Va
}
},
watch:{
"$route":"changebar"
}
}
</script>
<style lang="less" >
.mu-bottom-nav{
position: fixed!important;
bottom: 0px;
background: #fafafa!important;
z-index: 5;
}
</style>
诠释一下:
- 因为Muse-ui有部份款式用到了less,所以在这里我们须要npm装置一个less的依靠,装置好后即可运用。
npm install less less-loader --save
- 这里我们加载了一个底部导航,muse-ui的,官网可以查到相干代码。这里要注重的是,为了让用户体验更好,我们须要让我们的底部导航随当前路由变化而高亮。详细是用了一段JS代码。
watch看管路由变化并触发一个method:changebar(),这个函数会猎取当前的路由名,并把bottomNav的值设置为当前路由名——即高亮当前的路由页面
- playerBox.vue组件之所以放在主组件里,就是为了音乐在每个子页面都能播放,而不会因为跳转路由而住手播放。
2.popular.vue
这是引荐歌单界面,这里用到了一个轮播图插件,是基于vue的,运用起来比较轻易,直接用npm装置:
npm install vue-awesome-swiper --save
装置好后,一样在main.js
中注册:
import VueAwesomeSwiper from 'vue-awesome-swiper'
Vue.use(VueAwesomeSwiper)
然后我们来看页面的代码:
<template>
<div class="popular">
<!-- navbar here -->
<mu-appbar>
<div class="logo">
iPlayer
</div>
</mu-appbar>
<!-- banner here-->
<mu-card>
<swiper :options="swiperOption">
<swiper-slide v-for="(item,index) in banners" :key="index">
<mu-card-media>
<img :src="item.pic">
</mu-card-media>
</swiper-slide>
<div class="swiper-pagination" slot="pagination"></div>
</swiper>
</mu-card>
<div class="gridlist-demo-container" >
<mu-grid-list class="gridlist-demo">
<mu-sub-header>热点歌单</mu-sub-header>
<mu-grid-tile v-for="(item, index) in list" :key="index">
<img :src="item.coverImgUrl"/>
<span slot="title">{{item.name}}</span>
<mu-icon-button icon="play_arrow" slot="action" @click="getListDetail(item.id)"/>
</mu-grid-tile>
</mu-grid-list>
</div>
<div class="footer-rights">
<h4>版权归Godown Huang一切,请<a href="https://github.com/WE2008311">联络我</a>。</h4>
</div>
</div>
</template>
<script>
import {swiper,swiperSlide} from 'vue-awesome-swiper'
import axios from 'axios'
export default {
name: 'popular',
data(){
return{
swiperOption: {
pagination: '.swiper-pagination',
paginationClickable: true,
autoplay: 4000,
loop:true
},
banners:[],
list: []
}
},
components: {
swiper,
swiperSlide
},
computed:{
},
created(){
this.initPopular()
},
methods:{
initPopular(){
axios.get('http://localhost:3000/banner').then(res=> {
this.banners=res.data.banners;
}),
axios.get('http://localhost:3000/top/playlist/highquality?limit=8').then(res=> {
this.list=res.data.playlists;
})
},
getListDetail(id){
this.$router.push({path: '/songsList'})
this.$store.commit('playlist',id);
}
}
}
</script>
<style lang="css">
@media screen and (min-width: 960px){
.mu-card-media>img{
height: 400px!important;
}
.mu-grid-list>div:nth-child(n+2){
width:25%!important;
}
}
.mu-grid-tile>img{
width: 100%;
}
.gridlist-demo-container{
display: flex;
flex-wrap: wrap;
justify-content: space-around;
}
.gridlist-demo{
width: 100%;
overflow-y: auto;
}
.footer-rights>h4{
color: #e1e1e1;
font-weight: 100;
font-size:.056rem;
height:90px;
padding-top: 10px;
text-align: center;
}
</style>
这里要申明一下,上面的这些组件除了
playerBox
以外都要在main.js中注册才运用。注册要领遗忘的了话,回头看看我之前写的todolist的项目是怎样注册的。
在store.js
中增加playList函数:
playlist(state,id){
const url='http://localhost:3000/playlist/detail?id='+id;
axios.get(url).then(res=> {
state.playlist=res.data.playlist;
})
},
这里的页面mu
开首的基础都是用Muse-ui搭建起来的,Swiper
开首的则是轮播图插件。界面不庞杂,主假如三个部份,上面的轮播图,中心的热点歌单引荐,底部的版权信息。款式基础是模板,这里做了一个简朴的挪动端适配:在PC端歌单会以每排4个分两排的情势分列,在挪动端歌单则会以每排2个分四排的情势分列,适配的要领是媒体查询,经由过程转变歌单div
的宽度转变每行歌单的数量。
这里要注重的:
- 歌单的数据和轮播图都是用的网易云数据,所以没有开api是没法读取的,引入
axios
的部份可以先不写,也可以写好先放着。 - 这里
methods
和created
内里的内容都涉及到axios的要求,所以可以先不写,不影响款式显现。数据可以先用假数据替代。 - playList的目标是点击歌单的时刻,进入歌单详情页,同时依据通报进去的歌单id猎取歌单的详细数据,axios的地点是api的地点,须要加载api插件才运用。
3.play.vue
终究到了最中心的组件,之所以说它中心是因为这是播放界面,音频播放的长度、音频信息都邑在这里被显现,而播放器的中心功用——播放——也是在这里被操纵(播放/停息)。
看详细代码:
<template>
<div class="play">
<!-- navbar here -->
<mu-appbar>
<mu-icon-button icon="navigate_before" slot="left" v-on:click="backpage"/>
<div class="logo">
iPlayer
</div>
</mu-appbar>
<!-- player here-->
<div class="bgImg">
<img :src="audio.picUrl" />
<!-- 封面CD -->
<mu-avatar slot="left" :size="300" :src="audio.picUrl"/>
</div>
<div class="controlBar">
<mu-content-block>
{{audio.songName}} - {{audio.singer}}
</mu-content-block>
<div class="controlBarSlide">
<span class="slideTime">{{audio.currentTime}}</span>
<mu-slider v-bind:value="progressPercent" @change="editprogress" class="demo-slider"/>
<span class="slideTime">{{audio.duration}}</span>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'play',
data(){
return{
}
},
components: {
},
computed:{
audio(){
return this.$store.getters.audio;
},
progressPercent(){
return this.$store.getters.audio.progressPercent;
}
},
methods:{
backpage(){
window.history.go(-1);
},
editprogress(value){
this.$store.commit('editProgress',value)
}
}
}
</script>
<style lang="css">
@media screen and (max-width: 414px){
.bgImg .mu-avatar{
height: 260px!important;
width: 260px!important;
margin-left: -130px!important;
}
}
.bgImg{
position:fixed;
height:100%;
width:100%;
background: #fff;
z-index:-1;
}
.bgImg>img{
width: 100%;
filter:blur(15px);
-webkit-filter: blur(15px);
-moz-filter: blur(15px);
-ms-filter: blur(15px);
}
.bgImg .mu-avatar{
position: absolute;
left: 50%;
margin-left: -150px;
top: 30px;
}
.controlBar{
position: fixed;
width: 100%;
height: 180px;
background: #fff;
bottom: 0;
z-index: 11;
text-align:center;
}
.mu-slider{
width: 70%!important;
display: inline-block!important;
margin-bottom: -7px!important;
}
.slideTime{
width: 29px;
display: inline-block;
}
.mu-content-block{
font-size: 18px;
color: #777
}
.mu-slider{
display: inline-block;
margin:0 3px -7px;
width: 70%;
}
</style>
store.js
增加代码:
play(state){
clearInterval(ctime);
const playerBar=document.getElementById("playerBar");
const eve=$('.addPlus i')[0];
let currentTime=playerBar.currentTime;
let currentMinute=Math.floor(currentTime/60)+":"+(currentTime%60/100).toFixed(2).slice(-2);
let duraTime=playerBar.duration;
let duraMinute=Math.floor(duraTime/60)+":"+(duraTime%60/100).toFixed(2).slice(-2);
state.audio.progressPercent=((playerBar.currentTime/playerBar.duration)*100).toFixed(1);
if(playerBar.paused){
playerBar.play();
eve.innerHTML="pause";
state.audio.duration=duraMinute;
state.audio.currentTime=currentMinute;
ctime=setInterval(
function(){
currentTime++;
currentMinute=Math.floor(currentTime/60)+":"+(currentTime%60/100).toFixed(2).slice(-2);
state.audio.currentTime=currentMinute;
state.audio.progressPercent=((playerBar.currentTime/playerBar.duration)*100).toFixed(1);
},1000
)
}else {
playerBar.pause();
eve.innerHTML="play_arrow";
clearInterval(ctime);
}
},
audioEnd(state){
const playerBar=document.getElementById("playerBar");
const eve=$('.addPlus i')[0];
eve.innerHTML="play_arrow";
clearInterval(ctime);
playerBar.currentTime=0;
let currentTime=playerBar.currentTime;
let currentMinute=Math.floor(currentTime/60)+":"+(currentTime%60/100).toFixed(2).slice(-2);
state.audio.currentTime=currentMinute;
},
editProgress(state,progressValue){
const playerBar=document.getElementById("playerBar");
const eve=$('.addPlus i')[0];
let duraTime=playerBar.duration;
let duraMinute=Math.floor(duraTime/60)+":"+(duraTime%60/100).toFixed(2).slice(-2);
// console.log(progressValue);
clearInterval(ctime);
if(playerBar.paused){
playerBar.play();
eve.innerHTML="pause"
state.audio.duration=duraMinute;
}
let currentTime=playerBar.duration*(progressValue/100);
ctime=setInterval(
function(){
currentTime++;
currentMinute=Math.floor(currentTime/60)+":"+(currentTime%60/100).toFixed(2).slice(-2);
state.audio.currentTime=currentMinute;
state.audio.progressPercent=((playerBar.currentTime/playerBar.duration)*100).toFixed(1);
},1000
)
playerBar.currentTime=currentTime;
let currentMinute=Math.floor(currentTime/60)+":"+(currentTime%60/100).toFixed(2).slice(-2);
state.audio.currentTime=currentMinute;
},
- 如代码所示,我在顶部导航增加了一个
icon button
,款式来自Muse-ui
绑定了一个点击事宜backpage,点击后会回到上一个路由页面。这个须要合营之前的高亮底部导航icon,才完成返回上一路由的同时高亮相对应的icon。 - 还要注重的是,computed里有两个要领,第一个是猎取vuex内里的当前曲目信息;第二个则是猎取进度条的百分比信息,这个要领完成了数据的双向绑定,跟着背景设定的计时器,不断地更新,从而完成播放时进度条的变化。一样,这里的款式也是来自
Muse-ui
的Slider
。 - 这里有一个须要注重的坑是,Muse-ui自带了许多的函数,第一次写的时刻没有注重,在进度条上绑定了一个
mouseup
事宜,效果无效,厥后才发明,实在已自带了change
事宜,还可以完成挪动端的兼容。所以写代码的时刻一定要多看看官网文档。 - 关于
store.js
里的要领,play
是播放/停息,详细会依据当前音频文件的paused
(即是不是停息)来推断。总的道理是起首猎取音频的持续时刻,然后经由过程一个定时器,不断更新显现时刻,播放完成时,计时器住手。 - 计时器很症结,进度条和显现时刻的更新都须要它。然则计时器有个坑,假如把计时器声明放在
play
要领里,则没法在audioEnd
要领里住手计时器,所以这里我们须要在最外层先声明一个ctime
,然后再在play
要领里把定时器赋值给ctime
,如许我们就可以随时住手计时器了。 -
audioEnd
要领是播放住手时要做的事变,我们会把住手按钮切换成播放,把显现时刻修改掉,别忘了住手计时器。 -
editProgress
要领是点击或拖动进度条时做的事变,我们会转变当前音频的currentTime
,即当前时刻,假如音频是停息状况,我们要让它继承播放。
4.search.vue
这也是一个比较中心的一个功用,毕竟引荐的歌单只需几个。看代码:
<template>
<div class="search">
<!-- navbar here -->
<mu-appbar>
<mu-icon-button icon="navigate_before" slot="left" v-on:click="backpage"/>
<div class="logo searchLogo">
iPlayer
</div>
<mu-text-field icon="search" class="appbar-search-field" slot="right" hintText="想听什么歌?" v-model="searchKey"/>
<mu-flat-button color="white" label="搜刮" slot="right" @click="getSearch(searchKey)"/>
</mu-appbar>
<!-- banner here-->
<mu-list>
<template v-for="(item,index) in result.songs">
<mu-list-item :title="item.name" @click="getSong(item.id,item.name,item.artists[0].name,item.album.name,item.artists[0].id)">
<mu-avatar slot="leftAvatar" backgroundColor="#fff" color="#bdbdbd">{{index+1}}</mu-avatar>
<span slot="describe">
<span style="color: rgba(0, 0, 0, .87)">{{item.artists[0].name}} -</span> {{item.album.name}}
</span>
</mu-list-item>
<mu-divider/>
</template>
</mu-list>
<div class="footer-rights">
<h4>版权归Godown Huang一切,请<a href="https://github.com/WE2008311">联络我</a>。</h4>
</div>
</div>
</template>
<script>
export default {
name: 'search',
data(){
return{
searchKey:''
}
},
computed:{
result(){
return this.$store.getters.result;
}
},
components: {
},
methods:{
backpage(){
window.history.go(-1);
},
getSearch(value){
this.$store.commit('getSearch',value);
},
getSong(id,name,singer,album,arid){
this.$store.commit('getSong',{id,name,singer,album,arid});
this.$store.commit('play');
}
}
}
</script>
<style lang="less">
@media screen and (max-width: 525px){
.searchLogo{
display: none;
}
.appbar-search-field{
width: 200px!important;
}
}
.appbar-search-field {
color: #FFF;
margin-top: 10px;
margin-bottom: 0;
&.focus-state {
color: #FFF;
}
.mu-icon {
color: #FFF;
}
.mu-text-field-hint {
color: fade(#FFF, 54%);
}
.mu-text-field-input {
color: #FFF;
}
.mu-text-field-focus-line {
background-color: #FFF;
}
}
.footer-rights>h4{
color: #e1e1e1;
font-weight: 100;
font-size:.056rem;
height:90px;
padding-top: 10px;
text-align: center;
}
</style>
在store.js
里增加:
getSearch(state,value){
const url='http://localhost:3000/search?keywords='+value+'?limit=30';
axios.get(url).then(res=>{
state.result=res.data.result;
})
},
getSong(state,{id,name,singer,album,arid}){
const url="http://localhost:3000/music/url?id="+id;
const imgUrl="http://localhost:3000/artist/album?id="+arid;
const playerBar=document.getElementById("playerBar");
axios.get(url).then(res=>{
state.audio.location=res.data.data[0].url;
state.audio.flag=res.data.data[0].flag;
state.audio.songName=name;
state.audio.singer=singer;
state.audio.album=album;
})
axios.get(imgUrl).then(res=>{
state.audio.picUrl=res.data.artist.picUrl;
})
let currentTime=playerBar.currentTime;
let currentMinute=Math.floor(currentTime/60)+":"+(currentTime%60/100).toFixed(2).slice(-2);
let duraTime=playerBar.duration;
let duraMinute=Math.floor(duraTime/60)+":"+(duraTime%60/100).toFixed(2).slice(-2);
state.audio.duration=duraMinute;
state.audio.currentTime=currentMinute;
state.audio.progressPercent=((playerBar.currentTime/playerBar.duration)*100).toFixed(1);
}
注重,在有须要运用
axios
的组件一定要
import
,npm下载装置没必要多说了。
诠释一下这个组件的两个要领:
-
getSearch
是猎取搜刮效果,它被绑定再搜刮按钮上,初始页面是空缺,经由过程通报症结字,用axios
从api猎取搜刮效果,再把效果显现在页面上。 -
getSong
绑定在每个搜刮的效果上,有两个步骤,第一是getSong
,会把点击的歌曲设置为要播放的歌曲,并把相干信息通报给play.vue
,让它显现在响应的处所;第二个步骤,会播放歌曲,也就是上面的play
要领,详细没必要再说。 - 这里有一个坑,我们可以须要经由过程vuex通报参数,然则有时刻通报多个参数会涌现
undefined
的状况,这时刻我们要把参数们写成{参数一,参数二,参数三}
的情势。
5.songList
这个组件主假如歌单详情页,基础的款式和搜刮页一样,就是猎取歌单的内容差别,搜刮页面的列表是依据症结词猎取的,歌单详情页的列表是依据歌单id猎取的,猎取的体式格局都是经由过程axios。
<template>
<div class="songsList">
<!-- navbar here -->
<mu-appbar>
<mu-icon-button icon="navigate_before" slot="left" v-on:click="backpage"/>
<div class="logo">
iPlayer
</div>
</mu-appbar>
<!-- banner here-->
<div class="listBgImg">
<img :src="playlist.coverImgUrl" />
<!-- 封面CD -->
<mu-avatar slot="left" :size="120" :src="playlist.coverImgUrl"/>
</div>
<mu-list>
<mu-sub-header>{{playlist.name}}</mu-sub-header>
<template v-for="(item,index) in playlist.tracks">
<mu-list-item :title="item.name" @click="getSong(item.id,item.name,item.ar[0].name,item.al.name,item.ar[0].id)">
<mu-avatar :src="item.al.picUrl" slot="leftAvatar"/>
<span slot="describe">
<span style="color: rgba(0, 0, 0, .87)">{{item.ar[0].name}} -</span> {{item.al.name}}
</span>
</mu-list-item>
<mu-divider/>
</template>
</mu-list>
<div class="footer-rights">
<h4>版权归Godown Huang一切,请<a href="https://github.com/WE2008311">联络我</a>。</h4>
</div>
</div>
</template>
<script>
export default {
name: 'songsList',
data(){
return{
}
},
components: {
},
computed:{
playlist(){
return this.$store.getters.playlist;
}
},
methods:{
backpage(){
window.history.go(-1);
},
getSong(id,name,singer,album,arid){
this.$store.commit('getSong',{id,name,singer,album,arid});
this.$store.commit('play');
}
}
}
</script>
<style lang="css">
.listBgImg{
height:200px;
width:100%;
background: #fff;
overflow: hidden;
}
.listBgImg>img{
width: 100%;
filter:blur(30px);
-webkit-filter: blur(30px);
-moz-filter: blur(30px);
-ms-filter: blur(30px);
}
.listBgImg .mu-avatar{
position: absolute;
left: 50%;
margin-left: -60px;
top: 130px;
}
.mu-list .mu-sub-header{
/* position: absolute; */
top: 260px;
font-size: 16px;
/* text-align: center; */
}
</style>
没什么须要诠释的,注重我们在getSong
内里通报的多个参数。
6.playerBox.vue
<template>
<div class="playerBox">
<audio ref="myAudio" :src="audio.location" @ended="audioEnd" id="playerBar"></audio>
<div class="controlBarBtn" v-show="judgement()">
<mu-icon-button icon="skip_previous"/>
<mu-icon-button class="addPlus" icon="play_arrow" @click="play"/>
<mu-icon-button icon="skip_next"/>
</div>
</div>
</template>
<script>
export default {
name: 'playerBox',
data(){
return{
}
},
components: {
},
computed:{
audio(){
return this.$store.getters.audio;
}
},
methods:{
play(){
this.$store.commit('play');
},
audioEnd(event){
this.$store.commit('audioEnd',event);
},
judgement(){
let path=this.$route.path;
if(path=="/play"){
return true;
}else{
return false;
}
}
}
}
</script>
<style lang="less" >
.controlBarBtn{
position: absolute;
z-index:12;
width: 243px;
margin-left: -121.5px;
top: 83%;
left: 50%;
}
.controlBarBtn i.mu-icon{
font-size: 36px;
color: #03a9f4;
left: 50%;
margin-left: -18px;
position: absolute;
top: 10%;
}
.controlBarBtn .addPlus{
top: 16px;
width: 80px!important;
height: 80px!important;
margin: 0 30px!important;
}
.controlBarBtn .addPlus i.mu-icon{
font-size: 60px;
margin-left: -30px;
top: 10%;
}
</style>
这个页面比较简朴,播放器audio
标签,绑定了ended事宜,即播放完成后实行。
这里有一个坑,诠释一下:我把播放器按钮放在这里了,为何呢?之前我是放在play.vue
里的,然则我发明一个题目,就是经由过程点击歌单的歌曲播放时,没法转变播放/停息按钮,为何呢?因为我转变按钮的要领是用innerHTML
转变,我为何要用这类要领呢?因为Muse-ui的icon经由衬着,是以标签的值的情势涌现的。这就不能不猎取DOM了,然则假如把按钮写在play.vue
里,在歌单页面时是猎取不到指定DOM的,因为当前页面基础没有这个DOM!只需把按钮写在在主组件里的playerBox.vue
里,才猎取到指定DOM。
然则写在playBox.vue
里又有一个题目,按钮会出如今每个页面里,然则我们只需它出如今播放页面就好了,所以我们在这里要给按钮绑定一个v-show
,内里的内容就是推断是不是是在指定路由,假如是播放页面,就显现按钮,不是,就隐蔽按钮。
axios和网易云api
axios详细的设置我都在上面讲了,这里引见一款网易云的api和运用要领。
引见一下运用要领,进入git把它下下来,在命令行实行:
$ node app.js
在浏览器输入地点:
localhost:3000
看到弹出的页面就申明服务器启动胜利了。然后我们可以在文档里查到详细要求的数据,比方banner啊,歌单啊,搜刮啊,都能要求。我们看到前面写的axios要求里的地点,都是详细要求的地点。
这里要注重的是,这个api默许的是没有开启跨域的,看app.js
里有一段被隐蔽的代码就是跨域的相干设置,消除隐蔽即可。
bug和未完胜利用
现在还存在一个比较大的bug,就是在歌单点击播放时,点击第一次因为没办法猎取个去的url,没法播放,只需再点击一次才播放,这个bug临时还没有时刻处理,会尽快处理。
然后现在还没有完成的功用是播放列表,天然上一曲/下一曲按钮也没有用了,歌曲播放一遍也就住手了,这个功用不算难,抽空把它做出来。
参考资料
这个app参考了一些技术文章,给了我很大的启示,附上链接。
用vue百口桶写一个“以假乱真”的网易云音乐
DIY 一个本身的音乐播放器 2.0 来袭
结语
这个app前前后后,磨磨蹭蹭做了两个月,好歹总算是做完了。进修照样得找项目来做,虽然这个项目还很大略,然则照样get到许多知识点,关于我的进步照样蛮大的。
这类项目不算难,写过的人也多,所以百分之八十的题目都能百度出来,剩下的百分之二十,技术社区里提个问基础可以处理。项目照样得本身写一遍,写的过程当中才发明题目,也才想办法找到处理办法,事变总是会比你设想的要简朴一点。
项目不算大,但要一步步写下来总有可以有所脱漏,这里是我的GitHub,人人可以对照着看看有无脱漏。假如你喜欢我的项目,也愿望star或许fork一波~