写一个MP3播放器(vuejs+nodejs+mongodb)

写一个MP3播放器(vue-cli+element ui+express+mongoose)

最近刚学完vuejs,为了增进理解就写了个MP3播放器(用到了vuex,vue-router,es6)。可上传歌曲歌词封面文件,可管理歌曲,可点击封面放大图片等
项目地址:https://github.com/variinlkt/MP3-vueplayer
项目截图:
写一个MP3播放器(vuejs+nodejs+mongodb)_第1张图片
写一个MP3播放器(vuejs+nodejs+mongodb)_第2张图片
写一个MP3播放器(vuejs+nodejs+mongodb)_第3张图片
项目还有很多不完善的地方,欢迎交流

在这里总结一下在练手时遇到的知识点:

  • html5的audio对象属性、方法、事件

  • 图片旋转动画

  • 黑胶背景

  • 点击放大

  • 歌词滚动逻辑

  • 播放逻辑

  • 切换歌曲逻辑

  • 一些细枝末节

  • 部署上线

一.html5的audio对象属性、方法、事件

了解html5中的audio对象是写好一个音频播放器的关键

1.属性

audioTracks 返回表示可用音频轨道的 AudioTrackList 对象
autoplay 设置或返回是否在加载完成后随即播放音频
controller 返回表示音频当前媒体控制器的MediaController 对象
controls 设置或返回音频是否显示控件(比如播放/暂停等)
crossOrigin 设置或返回音频的 CORS 设置
currentSrc 回当前音频的 URL
defaultMuted 设置或返回音频默认是否静音
defaultPlaybackRate 设置或返回音频的默认播放速度
duration 返回当前音频的长度(以秒计)
error 返回表示音频错误状态的 MediaError 对象
loop 设置或返回音频是否应在结束时重新播放
mediaGroup 设置或返回音频所属的组合(用于连接多个音频元素)
networkState 返回音频的当前网络状态
paused 设置或返回音频是否暂停
played 返回表示音频已播放部分的 TimeRanges 对象
preload 设置或返回音频是否应该在页面加载后进行加载
readyState 返回音频当前的就绪状态
seeking 返回用户是否正在音频中进行查找
src 设置或返回音频元素的当前来源
textTracks 返回表示可用文本轨道的 TextTrackList 对象
volume 设置或返回音频的音量

该项目用到的属性:
volumecrossOrigin,durationsrc,currentTime,ended,loop
其中设置crossOrigin属性为“anonymous”,不设置就无法通过js来动态更改audio对象的src

2.方法

addTextTrack() 在音频中添加一个新的文本轨道
canPlayType() 检查浏览器是否可以播放指定的音频类型
fastSeek() 在音频播放器中指定播放时间。
getStartDate() 返回一个新的Date对象,表示当前时间轴偏移量
load() 重新加载音频元素
play() 开始播放音频
pause() 暂停当前播放的音频

这里用到loadplaypause

3.事件

当视频/音频(audio/video)已经加载后,视频/音频(audio/video)的时长从 “NaN” 修改为正在的时长。
在视频/音频(audio/video)加载过程中,事件的触发顺序如下:
onloadstart在浏览器开始寻找指定视频/音频(audio/video)触发。
ondurationchange在视频/音频(audio/video)的时长发生变化时触发。
onloadedmetadata在指定视频/音频(audio/video)的元数据加载后触发。
onloadeddata在当前帧的数据加载完成且还没有足够的数据播放视频/音频(audio/video)的下一帧时触发。
onprogress在浏览器下载指定的视频/音频(audio/video)时触发。
oncanplay在用户可以开始播放视频/音频(audio/video)时触发。
oncanplaythrough在视频/音频(audio/video)可以正常播放且无需停顿和缓冲时触发。

这里用到ondurationchange,用于切换歌曲时获取歌曲时长

二.图片旋转动画

写一个MP3播放器(vuejs+nodejs+mongodb)_第4张图片
这里可以看到:播放音乐时歌曲封面是随着歌曲播放而旋转的,播放暂停则暂停旋转
实现原理:css3的animation
html:

<div class="circle">
        <div class="circle-inside" :style="{backgroundImage: 'url('+cov+')',animationPlayState:ps?'running':'paused'}">
        div>
div>
//ps为当前的播放状况playstate

css:

.circle-inside{
  width: 100px;
  height: 100px;
  background: #fff;
  background-size: cover;
  position: relative;
  top:25%;
  left: 25%;
  border: 1px solid transparent;
  border-radius: 50%;
  transform: translate(-50%,-50%);
  transform-origin: 50%,50%;
  animation: spin 10s linear infinite;//animation
  transition: transform .5s;
  transform: scale(1,1);
}

@keyframes spin{
  from{
    transform: rotate(0deg);
  }
  to{
    transform: rotate(360deg);
  }
}

三.黑胶背景

实现原理:linear-gradient

.circle{
  border: 1px solid transparent;
  background: linear-gradient(45deg,#1B1B1B 35%,#777676 50%,#1B1B1B 65%);//linear-gradient
  width: 200px;
  height: 200px;
  border-radius: 50%;
}

四.点击放大

项目中可以点击封面放大如下图所示:
写一个MP3播放器(vuejs+nodejs+mongodb)_第5张图片
实现原理:css3的transition&transform

.circle-inside{
  width: 100px;
  height: 100px;
  background: #fff;
  background-size: cover;
  position: relative;
  top:25%;
  left: 25%;
  border: 1px solid transparent;
  border-radius: 50%;
  transform: translate(-50%,-50%);//transform
  transform-origin: 50%,50%;//缩放中心
  animation: spin 10s linear infinite;
  transition: transform .5s;
  transform: scale(1,1);
}
.circle-inside:active{
  transform: scale(2,2);
  animation: none;
}

五.歌词滚动逻辑

  1. 在载入页面时计算当前屏幕高度,算出歌词div的marginTop

  2. 在载入页面时获取歌词文件及每句歌词的时间,分别放入lrcData和tarr数组中

  3. 获取当前时间ct(currenttime)并侦听,设置idx(当前播放第几句歌词),如果发现当前时间ct比tarr[idx]大,则marginTop-30px,idx++

    由以上逻辑我们能写出代码:

//script
mounted(){
    this.calMarginT(this)//1
    this.postData(localStorage.getItem('lrc'),this)//2
},
watch:{//3
    ct(){
      this.handleTime(this.tarr,this)
    }, 
}
computed:{
    ct(){
        return this.$store.state.current
    }
}
//main.js
Vue.prototype.postData=(url,that)=>{
    let lrc=[],tarr=[]
  axios.post('api/getLrc',{
    lrc:url
  })
  .then(function(response){
    if(response.data){
      let lyric=response.data
      let larr=lyric.split('\n')
      larr.forEach((val,i)=>{
        let t=val.split(']')[0]
        let time=t.split('[')[1]
        let l=val.split(']')[1]

        lrc.push(l)

        tarr.push(parseInt(time.split(':')[0])*60+parseFloat(val.split(':')[1]))

        that.lrcData=lrc
        that.tarr=tarr
      })
    }
  })
}
Vue.prototype.calMarginT=(that)=>{
  that.marginT=parseInt(window.innerHeight)/2-100
}
Vue.prototype.handleTime=(tarr,that)=>{
  if(that.ct>=tarr[that.idx]){
    that.marginT-=30
    that.idx++
  }
}

但是如果我们拉动了进度条就会出bug

解决办法:

Vue.prototype.sliderChange=(newct,tarr,marginT,idx,that)=>{
  let minv=10000,mini
  tarr.forEach((val,i)=>{
    if(Math.abs(val-newct)
  that.marginT+=(idx-mini)*30
  that.idx=mini
}//在拉进度条时调用

六.播放逻辑

playMusic(url=localStorage.getItem('url'),that=this){
      let audi=document.getElementById('audi')
      audi.setAttribute("crossOrigin","anonymous")
      audi.setAttribute("src","")
      audi.setAttribute("src",url) 
      audi.load()
      audi.onduratiοnchange=()=>{//ondurationchange在事件循环最后执行
        that.d=audi.duration
      }

      if(!that.ps) {  //play
        that.iconChange='el-icon-error'//更改图标
        audi.play()
      } else {  //pause
        that.iconChange='el-icon-caret-right'
        audi.pause()
      } 
      if(that.ct!==audi.currentTime){//暂停时更改播放位置,否则相当于停止
        audi.currentTime=that.ct
      }
      that.ps=!that.ps//更改播放状态
},

七.切换歌曲逻辑

  1. 初始化marginTop,currentTime,tarr(歌词时间【数组】),idx(当前播放歌词的idx),lrc(歌词【数组】)playState=false
  2. 通过当前的playIdx(当前播放的歌曲序号)获得下一首播放歌曲信息
  3. 将信息存储至localStorage
  4. 播放音乐
  5. 计算marginTop
  6. 获取歌词
  7. 更改src
//store.js
mutations:{//1
    update(state,info){//多参数必须是个对象
       state.playSong=info.song
        state.playSinger=info.singer
        state.playCov=info.cov
        state.playLrc=info.lrc
        state.playUrl=info.url
        state.playIdx=info.idx
    },
    init(state){
        state.current=0
        state.marginT=0
        state.tarr=[]
        state.idx=0
        state.lrc=[]
        state.playState=false
    },
},
//App.vue
changeSong(s){
  let audi = document.getElementById('audi')
  audi.setAttribute("src","") //停止当前歌曲的播放
  let i=this.playIdx
  if(s==='next'){//点击下一首
    i++
    if(i===this.tableLength)//如果当前歌曲是最后一首歌
      i=0
  }
  else if(s==='pre'){//点击上一首
    i--
    if(i<0)
      i=this.tableLength-1
  }
  else i=s
  store.commit('update',{
    song:this.tableData[i].song,
    singer:this.tableData[i].singer,
    cov:this.tableData[i].cov,
    lrc:this.tableData[i].lrc,
    url:this.tableData[i].url,
    idx:this.tableData[i].idx
  })

  this.setStorage(this.song,this.singer,this.cov,this.url,this.lrc)
  this.playMusic(this.url)
  this.calMarginT(this)
  this.postData(localStorage.getItem('lrc'),this)

},
//main.js
Vue.prototype.setStorage=(song,singer,cov,url,lrc)=>{
  if(!localStorage.getItem('song')){//播放第一首
    localStorage.setItem('song',song)
    localStorage.setItem('singer',singer)
    localStorage.setItem('cov',cov)
    localStorage.setItem('url',url)
    localStorage.setItem('lrc',lrc)
  }else{
    if(localStorage.getItem('url')!==url){//播放下一首
      if(url){
        localStorage.setItem('song',song)
        localStorage.setItem('singer',singer)
        localStorage.setItem('cov',cov)
        localStorage.setItem('url',url)
        localStorage.setItem('lrc',lrc)
      }

    }
  }
}

八.一些细枝末节

  • 从store里面获取/更改数据:
computed:{//要写在computed中而不是data中
    marginT:{
      get: function () {//getter
        return this.$store.state.marginT
      },
      set: function (newVal) {//setter
        this.$store.state.marginT=newVal
      }
    }
}
  • mutation里面的方法如果有多参数:
 update(state,info){//info必须是个对象
    state.playSong=info.song
    state.playSinger=info.singer
    state.playCov=info.cov
    state.playLrc=info.lrc
    state.playUrl=info.url
    state.playIdx=info.idx
},
  • :style="{backgroundImage: 'url('+cov+')',animationPlayState:ps?'running':'paused'}"
  • 如果想直接使用mutation里面的方法而不是this.$store.commit()
import {mapMutations} from 'vuex'
...mapMutations([
    'update','init'
]),

九.部署上线

待完善中…

你可能感兴趣的:(项目)