欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

vue-music (12) 播放内核

程序员文章站 2022-03-07 17:18:48
...

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

1.手写动画的模板

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

这是自定义动画,需要手写
vue-music (12) 播放内核

_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()
      }
    },

自带几个方法
aaa@qq.com:能不能进行播放
aaa@qq.com:不能播放时反馈
aaa@qq.com:实时更新播放进度
aaa@qq.com:播放结束后
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

相关标签: vue