vue-music (12) 播放内核

最重要的就是这个播放器播放页面
从整体来看,点击list列表的歌曲会储存到vuex中,然后进入play页面,首先给进入来一个动画

1.手写动画的模板

<transition name="normal" @enter="enter" @after-enter="afterEnter" @leave="leave" @after-leave="afterLeave">

这是自定义动画,需要手写
这里写图片描述

_getPosAndScale () { // 基础配置,// 算出从左下角到最终地点的偏移量,和大小比例 起点到终点  x y也可以自定义,只不过就不是从左下角开始罢了
      const targetWidth = 40
      const paddingLeft = 40
      const paddingBottom = 30
      const paddingTop = 80
      const width = window.innerWidth * 0.8 // window.innerWidth窗口的文档显示区的宽度,以像素计
      const scale = targetWidth / width
      const x = -(window.innerWidth / 2 - paddingLeft) // 从左下角开始偏移,所以是负值
      const y = window.innerHeight - paddingTop - width / 2 - paddingBottom
      return {
        x,
        y,
        scale
      }
}
enter(el, done){ // done是回调函数 即执行下一个函数
    const {x, y, scale} = this.__getPosAndScale()
    let animation = {
        0: {
          transform: `translate3d(${x}px, ${y}px, 0) scale(${scale})`
        },
        60: {
          transform: `translate3d(0, 0, 0) scale(1.2)` // 放大
        },
        100: {
          transform: `translate3d(0, 0, 0) scale(1.0)` // 回复原来大小
        }
    }
    // 注册动画
    animations.registerAnimation({
        name: 'move',
        animation, // 自定义得到动画
        // 参数设置
        presets: {
          duration: 1000, // 持续时间
          easing: 'linear', // 过渡效果
          delay: 50 // 延时
        }
     }
     // 执行动画 
     animations.runAnimation(this.$refs.要发生动画的部分,'move', () => {
         done() // 执行后续函数
     })
 },
 afterEnter () {
      // 取消动画
      animations.unregisterAnimation('move')
      this.$refs.cdWrapper.style.animation = ''
},
leave (el, done) {
      this.$refs.cdWrapper.style.transition = 'all 0.4s'
      const {x, y, scale} = this._getPosAndScale()
      this.$refs.cdWrapper.style[transform] = `translate3d(${x}px, ${y}px, 0) scale(${scale})`
      // this.$refs.cdWrapper.addEventListener('transformend', done)
      // 执行动画
      this.$refs.cdWrapper.addEventListener('transitionend', () => { //动画执行完毕之后
            done()
      })
},
afterLeave () {
    //取消动画
      this.$refs.cdWrapper.style.transition = ''
      this.$refs.cdWrapper.style[transform] = ''
},       

cdwrapper中的图片随着播放而旋转是通过设置‘play’和’pouse’属性得到
而这两个属性也是动态计算得到

            <div class="cd" :class="cdCls" >
                <img :src="currentSong.imageurl" alt="" class="image">
            </div>
 computed:{
     cdCls () {
      return this.playing ? 'play' : 'play pause' // this.playing 状态是根据vuex得到,全组件通用
    },
}       
...
        &.play {
                animation: rotate 20s linear infinite;
              }
        &.pause {
              animation-play-state: paused;
              }

音频播放

<audio ref="audio" :src="currentSong.url" @play="ready" @error="error" @timeupdate="timeUpdate" @ended="songEnd"></audio>

    ready () {
      this.songReady = true
      this.savePlayHistory(this.currentSong)
    },
    error () {
      this.songReady = true
    },
    timeUpdate (e) {
      this.currentTime = e.target.currentTime // 动态获得播放当前时间
    },
    songEnd () { // 判定是单曲循环还是播放下一曲
      if (this.mode === playMode.loop) {
        this.loop()
      } else {
        this.next()
      }
    },

自带几个方法
1.@play:能不能进行播放
2.@error:不能播放时反馈
3.@timeupdate:实时更新播放进度
4.@ended:播放结束后
play():audio自带方法 播放
pause(): audio自带方法 暂停
下一曲:

next () {
      if (!this.songReady) { // songReady是标志位,当歌曲能播放时,默认false,只有error返回为能播放时,变为ture,进而return跳过,执行下面的函数
        return
      } else {
        if (this.playList.length === 1) { // 歌曲列表只有一首歌,单曲循环
          this.loop()
          return
        }
        let index = this.currentIndex + 1 // 因为是下一首,所以所引致加一
        if (index === this.playList.length) { // 最后一首歌, 返回到第一首
          index = 0 
        }
        this.setCurrentIndex(index) // actions的方法,根据index决定播放的具体是哪首
        if (!this.playing) { // 调整播放状态
          this.togglePlaying()
        }
      }
      this.songReady = false // 改回标志位状态
    },

上一曲

prev () {
      if (!this.songReady) {
        return
      }
      if (this.playList.length === 1) {
        this.loop()
        return
      } else {
        let index = this.currentIndex - 1 // 上一首, 索引值减一
        if (index === -1) { // 第一首 的前一曲
          index = this.playList.length - 1 // 最后一曲
        }
        this.setCurrentIndex(index)
        if (!this.playing) {
          this.togglePlaying()
        }
      }
      this.songReady = false
    },

单曲循环

    loop () { // 循环播放
      this.$refs.audio.currentTime = 0 // 显示时间跳回到0秒
      this.$refs.audio.play() // audio自带方法
      if (this.currentLyric) {
        this.currentLyric.seek(0) // 歌词 跳转到开头部分
      }
    },

歌词

<scroll class="middle-r" ref="lyricList" :data="currentLyric && currentLyric.lines">
            <div class="lyric-wrapper">
              <div v-if="currentLyric">
                <p class="text" :class="{'current': currentLine === index}" v-for="(line, index) in currentLyric.lines" :key="line.id" ref="lyricLine">
                  {{line.txt}}
                </p>
              </div>
            </div>
</scroll>

    getLyric () {
      this.currentSong.getLyric().then((lyric) => { // 歌词不符,跳过
        if (this.currentSong.lyric !== lyric) {
          return
        }
        this.currentLyric = new Lyric(lyric, this.handelLyric) // 创建实例
        if (this.playing) {
          this.currentLyric.play() // 自带方法,歌词播放
        }
        // console.log(this.currentLyric)
      }).catch(() => { // 寻不到歌词时候 的错误处理
        this.currentLyric = null
        this.playingLyric = ''
        this.currentLine = 0
      })
    },
    handelLyric ({lineNum, txt}) { // 处理一下歌词,保证当前播放歌词始终出现在屏幕中间
      this.currentLine = lineNum
      if (lineNum > 5) {
        let lineEl = this.$refs.lyricLine[lineNum - 5]
        this.$refs.lyricList.scrollToElement(lineEl, 1000)
      } else {
        this.$refs.lyricList.scrollTo(0, 0, 1000)
      }
      this.playingLyric = txt
    },

横滑中间区域,显示/隐藏歌词、

<div class="middle" @touchstart.prevent="middleTouchStart" @touchmove.prevent="middleTouchMove" @touchend.prevent="middleTouchEnd">

created () {
    this.touch = {} // 先创建一个touch事件
},

    middleTouchStart (e) {
      this.touch.initiated = true
      const touch = e.touches[0]
      this.touch.startX = touch.pageX // 记录开始触摸的x, y
      this.touch.startY = touch.pageY
    },
    middleTouchMove (e) {
      if (!this.touch.initiated) {
        return
      }
      const touch = e.touches[0]
      const deltaX = touch.pageX - this.touch.startX // 计算出x,y偏移量
      const deltaY = touch.pageY - this.touch.startY
      if (Math.abs(deltaY) > Math.abs(deltaX)) { // 若y偏移量大,说明是下滑,不是左滑
        return
      }
      const left = this.currentShow === 'cd' ? 0 : -window.innerWidth // 确定当前页面是哪个,并由此得出left值
      const offsetWidth = Math.min(0, Math.max(-window.innerWidth, left + deltaX))
      this.touch.percent = Math.abs(offsetWidth / window.innerWidth)
      this.$refs.lyricList.$el.style[transform] = `translate3d(${offsetWidth}px, 0, 0)`
      this.$refs.lyricList.$el.style[transitionDuration] = 0 // 动画
      this.$refs.middleL.style.opacity = 1 - this.touch.percent // 透明度
      this.$refs.middleL.style[transitionDuration] = 0 // 动画
    },
    middleTouchEnd () {
      let offsetWidth
      let opacity
      if (this.currentShow === 'cd') {
        if (this.touch.percent > 0.1) { // 从有往左滑动
          offsetWidth = -window.innerWidth
          opacity = 0 // 透明
          this.currentShow = 'lyric'
        } else {
          offsetWidth = 0
          opacity = 1
        }
      } else {
        if (this.touch.percent < 0.9) { // 从左往右滑动
          offsetWidth = 0
          opacity = 1
          this.currentShow = 'cd'
        } else {
          offsetWidth = -window.innerWidth
          opacity = 0
        }
      }
      const time = 300
      this.touch.percent = Math.abs(offsetWidth / window.innerWidth)
      this.$refs.lyricList.$el.style[transform] = `translate3d(${offsetWidth}px, 0, 0)`
      this.$refs.lyricList.$el.style[transitionDuration] = `${time}ms` // 动画
      this.$refs.middleL.style.opacity = opacity
      this.$refs.middleL.style[transitionDuration] = `${time}ms` // 动画
    },

监听事件

watch: {
    currentSong (newsong, oldsong) { // 当前播放的状态
      if (!newsong.id) { // 没有播放
        return
      }
      if (newsong.id === oldsong.id) { // 播放的是同一首
        return
      }
      if (this.currentLyric) {
        this.currentLyric.stop()
      }
      clearTimeout(this.timer)
      this.timer = setTimeout(() => {
        this.$refs.audio.play() // 观察当前歌曲发生变化就 播放歌曲
        this.getLyric() // 获得歌词 渲染dom
      }, 1000)
    },
    playing (newPlaying) {
      if (!this.songReady) {
        return
      }
      const audio = this.$refs.audio
      // 因为当音乐还没有获取的时候,不能调用play,所以要用$nextick 将回调延迟到下次 DOM 更新循环之后执行。在修改数据之后立即使用它,然后等待 DOM 更新
      this.$nextTick(() => { // 异步获取 在currentSong变化的时候,去调用play()方法:
        newPlaying ? audio.play() : audio.pause()
      })
      // this.songReady = false
    }
},

获得当前播放时间 进度条设置

    timeUpdate (e) {
      this.currentTime = e.target.currentTime // audio自带方法 动态获得播放当前时间,但得到的是时间戳
    },
    formate (interval) { // 转换时间戳为时间格式
      interval = interval | 0 // 向下取整 等于 math.floor()
      const minute = interval / 60 | 0
      const second = this._pad(interval % 60) 
      return `${minute}:${second}`
    },
    // 补零函数
    _pad (num, n = 2) { // 一位数补0凑两位数函数
      let len = num.toString().length // 数字变字符串,得到位数个数
      while (len < n) { // 当num为一位数时,进入循环
        num = '0' + num
        len++ // 跳出条件
      }
      return num
    },

https://blog.csdn.net/weixin_40814356/article/details/80375143

猜你喜欢

转载自blog.csdn.net/weixin_42372054/article/details/82430449