播放功能

播放功能是使用H5的Audio标签实现

先说下思路:

  1. 使用 audio 作为音乐的播放器
  2. 在歌曲被点击的时候,改变vuex的全局播放状态,并且在播放状态变更的时候 控制 audio 播放或则暂停
  3. 相关的操作包括控制按钮的样式 都依赖 这个全局的播放状态。可以使用计算属性进行返回class样式名称
 <audio ref="audio" :src="currentSong.url"></audio>
    computed: {
      ...mapGetters([
        'fullScreen',
        'playlist',
        'currentSong',
        'playing'
      ]),
      playIcon () {
        return this.playing ? 'icon-pause' : 'icon-play'
      },
      miniIcon () {
        return this.playing ? 'icon-pause-mini' : 'icon-play-mini'
      }
    },
    methods: {
      /* 切换播放状态  */
      togglePlaying () {
          // 更改vuex中的playing值,还要编写控制Audio停止播放的代码,所以在watch中去监听这个播放状态
          this.setPlaying(!this.playing)
        }
      },
      watch: {
        // 当前歌曲变化的时候 播放歌曲
        currentSong () {
          // 在dom没有变化之前调用play会出错,所以使用vue提供的dom更新后调用
          this.$nextTick(() => {
            this.$refs.audio.play()
          })
        },
        playing (staus) {
          // 这里暂时没有发现 有报错,可能和浏览器版本有关吧
          staus ? this.$refs.audio.play() : this.$refs.audio.pause()
        }
      }
    }

由于这里只是一些简单的样式和audio的控制,就不详细贴代码了。

在使用中出现了以下异常:

Uncaught (in promise) DOMException: The play() request was interrupted by a new load request.

经过排查定位在 watch-playing中,在首次点击歌曲的时候,会更改vuex中的播放状态,同时会给audio标签的src赋值歌曲地址,如果这个时候,dom还没有被更新(地址还没有赋值上),那么这个时候调用播放按钮就会报错。

所以还是需要更改为 $nextTick 的方式

      playing (staus) {
        // 这里暂时没有发现 有报错,可能和浏览器版本有关吧
        console.log(staus)  // 通过增加日志定位异常
        this.$nextTick(() => {
          staus ? this.$refs.audio.play() : this.$refs.audio.pause()
        })
      }

对于 nextTick 在vue的官网中有介绍,大致意思就是说,从数据到dom有一个异步队列更新的机制,这个机制就会造成不是非常精准的实时更新,所以提供了这么一个入口。来防止这种情况的发生

1. 迷你播放器播放按钮bug

按照上面的思路完整了播放的效果。可是在迷你播放器播放按钮上点击的时候,状态是改变了,但是同时还打开了全屏的播放器。这里有一个子元素的冒泡事件,如下

<transition name="mini">
      // 这里是切换到全屏播放器的事件
      <div class="mini-player" v-show="!fullScreen" @click="open">
        <div class="icon">
          <img width="40" height="40" :src="currentSong.image">
        </div>
        <div class="text">
          <h2 class="name" v-html="currentSong.name"></h2>
          <p class="desc">{{currentSong.singer}}</p>
        </div>
        // 这里是子元素的 播放控制按钮
        <div class="control">
          <i :class="miniIcon" @click.stop="togglePlaying"></i>
        </div>
        <div class="control">
          <i class="icon-playlist"></i>
        </div>
      </div>
    </transition>

所以这里使用 vue提供的 @click.stop 阻止冒泡到父元素

2. 上/下一首控制功能

对于前面已经铺好了路,所以这里的思路也比较简单:上/下一首的时候,更改当前播放歌曲的索引,就能播放上/下一首歌曲了

    methos:{
      prev () {
        this.setCurrentIndex(this.currentIndex - 1)
        // 处理边界
        // 在顺序播放模式下才有这样的边界首尾相链接的处理
        if (this.currentIndex === -1) {
          this.setCurrentIndex(this.playlist.length - 1)
        }
        // 还要处理一种情况,如果 歌曲现在是暂停状态,上/下一首之后也需要切换到播放状态
        if (!this.playing) {
          this.togglePlaying()
        }
      },
      next () {
        this.setCurrentIndex(this.currentIndex + 1)
        if (this.currentIndex === this.playlist.length) {
          this.setCurrentIndex(0)
        }
        if (!this.playing) {
          this.togglePlaying()
        }
      }
    }

然而还有一个bug,在快速点击 上/下一首的时候,也会出现 Uncaught (in promise) DOMException: The play() request was interrupted by a new load request. 错误,这个错误可能也是因为dom刷新不及时造成的。那么就要在歌曲可以播放后,才能点击上/下一首的功能

那怎么才能知道歌曲是否可以播放了呢?

利用audio标签的事件 canplayerror 事件来处理,可以播放和播放出错或则,网络出错的问题。

<audio ref="audio" :src="currentSong.url" @canplay="ready" @error="error"></audio>
      ready () {
        this.songReady = true
      },
      error () {
        this.songReady = true
      }

思路:

  1. 使用 songReady 来标识是否可以点击上/下一首的控制按钮
  2. 在歌曲可以播放的时候 改变为 true,在出错的时候也要改变为true,不然下一首功能就不可用了
  3. 在歌曲切换后,改变为 false
  4. 再加上不可点击按钮的样式,样式依赖 这个 songReady的属性

线插播一个样式的bug

3. 播放器cd在不播放的时候没有透明边框

如果上图,只有cd加上转动的动画效果,边框才会把图片容纳进来,我也不知道是为什么,如果不加转动动画的话,图片大小和外面的边框大小是一致的,所以看不到边框。

但是加上动画效果,的时候,图片就被容纳在边框中了。 而且更符合现实,因为一开始就有动画css,但是动画暂停的,所以在切换播放/暂停的时候,cd转动的位置不会像之前一样,暂停后,位置和样式都还原了

到目前为止,有了播放/暂停,上一首/下一首 的功能,接下来要做播放进度条功能

4. 播放时间和更新

播放器样式效果图如下(外围架子,控制部分抽取成组件):

如上样式,分为3部分,左中右,左右各占30px

 <div class="progress-wrapper">
   <span class="time time-l">0.00</span>
   <div class="progress-bar-wrapper"></div>
   <span class="time time-r">04.60</span>
</div>
       .progress-wrapper {
          display flex
          align-items center
          width 80%
          margin 0px auto
          padding 10px 0
          .time {
            color $color-text
            font-size $font-size-small
            flex 0 0 30px
            line-height 30px
            width 30px
            &.time-l {
              text-align left
            }
            &.time-r {
              text-align right
            }
          }
          .progress-bar-wrapper {
            flex 1
          }
        }

这里要获取 当前播放时间,和歌曲的总时间。

  1. 当前播放时时间,利用 audio的timeupdate事件得到
  2. 歌曲的总时间从当前播放歌曲数据中得到
    // 进度条部分
    <div class="progress-wrapper">
            <span class="time time-l">{{format(currentTime)}}</span>
            <div class="progress-bar-wrapper"></div>
            <span class="time time-r">{{format(currentSong.duration)}}</span>
          </div>
    // audio 播放标签      
    <audio ref="audio" :src="currentSong.url"
           @canplay="ready" @error="error"
           @timeupdate="timeupdate"
    ></audio>
  methods:{
      timeupdate (e) {
        this.currentTime = e.target.currentTime
      },
      /** 格式化时间,单位秒,返回 00:59  这样的 分:秒 字符串 */
      format (interval) {
        interval = interval | 0  // 向下取整,同函数Math.floor(7/2)相同功能
        const minute = interval / 60 | 0
        const second = interval % 60
        // 这里有一个问题,返回的秒小于10的时候没有补0填充,需要编写一个工具方法补0
        return `${minute}:${this._pad(second)}`
      },
      /*  pad 有填充的意思
       *  num : 数字
       *  n : 要填充的位数
       */
      _pad (num, n = 2) {
        // 这里针对秒做补零操作
        let len = num.toString().length
        let result = num
        while (len < n) {
          result = '0' + result
          len++
        }
        return result
      }
  }
© All Rights Reserved            updated 2017-12-28 02:49:41

results matching ""

    No results matching ""