播放器模式切换

1. 样式切换

这个太简单了,在前面已经写过好多个按钮图标的切换了。这里依赖的是 定义的全局属性 model.思路如下:

  1. 在计算属性中返回model值
  2. 在计算属性中 根据model值返回对应的icon图标样式
  3. 给按钮增加点击事件,并改写全局属性的model值

       playModelChange () {
         let mode = (this.mode + 1) % 3  // 循环3次切换
         this.setPlayModel(mode)
       }
    

2. 歌曲相关信息切换

这里切换播放模式的话,歌曲列表也是会打乱顺序然后被切换的(与自己想的不同,我一直以为列表是原始列表,只是播放的时候使用的是随机数播放)

所以 这里要做以下操作:

  1. 改变vuex中的播放列表(根据不同的播放模式)
  2. 如果播放列表改变了,当前歌曲的索引也要改变
 playModelChange () {
        let mode = (this.mode + 1) % 3  // 循环3次切换
        this.setPlayModel(mode)
        let list = null
        if (mode === playMode.random) {
          // 获得原始列表,打乱顺序后,再赋值给播放列表
          list = shuffle(this.sequenceList)
        } else {
          list = this.sequenceList
        }
        this.setPlayList(list)
        this._resetCurrentIndex(list)
      },
      _resetCurrentIndex (list) {
        let songId = this.currentSong.id
        let index = list.findIndex((item) => {
          return item.id === songId
        })
        // 在getters中 currentSong 是根据索引获取到歌曲属性对象的
        // 这里索引改变了,currentSong 也改变了,但是对象一样的,
        // 因为在shuffle中现在的实现是在原来的列表中打乱顺序的,他们的内存地址一样
        // 所以 为什么 还会触发watch中的currentSong事件呢?没有搞明白
        // 但是现象就是这样的
        this.setCurrentIndex(index)
      }

上面用到的工具类 src/common/js/util.js

function getRandomInt (min, max) {
  // random() 返回 0 -0.9999 之间的小数,
  // 假设 min=0,max=20, 0.99 * (20-0 +1) = 20.79 + 0 ,再向下取整 就得到了最大值20
  // 最小值也是一样。不错这个
  return Math.floor(Math.random() * (max - min + 1) + min)
}

/**
 * 洗牌:这里现在的实现 我觉得有问题,在原有基础上洗牌的,
 * 根据功能来说,是完全没有毛病的;在是在外面使用的场景中,外面把原始列表传递进来了。原始列表的顺序都被改变了
 * @param arr
 */
export function shuffle (arr) {
  let length = arr.length
  for (let i = 0; i < length; i++) {
    let j = getRandomInt(0, i)
    let a1 = arr[j]
    arr[j] = arr[i]
    arr[i] = a1
  }
  return arr
}

上面的代码写好后,播放模式个列表都ok了,大事有一个与之前冲突的地方:

如果当前正则暂停中,这个时候再切换播放模式的话,会让歌曲再次播放.解决方案如下:

  1. 先定位:改变了播放列表 和 歌曲索引,那么歌曲索引会英法 currentSong的变化
  2. currentSong的变化 会触发下面的watch中的 播放操作
  3. 只要对比下 新值和旧值的歌曲id 就知道是否是同一首歌曲了

    只是这里没有明白的是,vue的watch工作原理判定是否相同的标准是什么?这个用 === 判定是相等,那为什么还会触发 watch 事件呢?

 watch: {
      // 当前歌曲变化的时候 播放歌曲
      currentSong (newSong, oldSong) {
        console.log('对比结果:', oldSong === newSong, ' oldSong:', oldSong, ' newSong:', newSong)
        // 解决切换播放模式后,该事件就算歌曲 对比结果是相同的情况下还是会触发watch事件
        // 如果这时 在暂停状态,那么如果没有这里的判断,就会触发下面的 play操作
        if (newSong.id === oldSong.id) {
          return
        }
        // 在dom没有变化之前调用play会出错,所以使用vue提供的dom更新后调用
        this.$nextTick(() => {
          this.$refs.audio.play()
        })
      },
}

3. 功能优化 - 歌曲结束后,自动播放下一曲

在前面的功能中,发现歌曲播放结束后,歌曲就不会再播放了。

audio 标签提供一个 onended事件,可以用来触发业务动作

   <audio ref="audio" :src="currentSong.url"
           @canplay="ready" @error="error"
           @timeupdate="timeupdate"
           @ended="onended"
    ></audio>
  // 歌曲播放结束
      onended () {
        if (this.mode === playMode.loop) {
          // 如果是循环播放,则歌曲单曲循环 -- 尼玛,原来是单曲循环
          this.loop()
        } else {
          this.next()
        }
      },
      loop () {
        this.$refs.audio.currentTime = 0
        this.$refs.audio.play()
      }

4. 功能优化 - 终于发现进度条点击两下获取的偏移值有问题了

在之前写长进度条的时候,发现连续点击两下进度条,进度条的宽度错得很离谱。

原来是 我们的点击事件写在了最外层的div上。而连续点击两次的时候,第一次是正常的,而第二次点击的时候是点到了 小圆点的按钮上了。所以获取到的偏移量是小远点的偏移量。所以看起来错得很离谱

  <div class="progress-bar" ref="progressBar" @click="progressClick">
    <div class="bar-inner">
      <div class="progress" ref="progress"></div>
      <div class="progress-btn-wrapper" ref="progressBtnWrapper"
           @touchstart="onTouchStart"
           @touchmove="onTouchMove"
           @touchend="onTouchEnd"
      >
        <div class="progress-btn"></div>
      </div>
    </div>
  </div>
progressClick (e) {
        // offsetX/y 能反应在当前dom上点击的左边,x轴,所以x轴的值就是 要移动到的宽度
//        let progressWidth = Math.min(e.offsetX, this._getProgressBarWidth())

        // 修复在点击到小圆点按钮上获取到的偏移量错误的bug
        // 获取点击在页面中的x轴偏移量,然后再减去进度条距离左边的间距
        let rect = this.$refs.progressBar.getBoundingClientRect() 
        console.log('rect:', rect)
        let progressWidth = Math.min(e.pageX - rect.left, this._getProgressBarWidth())
        this._setProgressWidth(progressWidth)
        console.log(progressWidth)
        this._triggerPercent()
      }

我打印了下获取到的rect 信息如下,就是这个dom元素的三围,加上距离左边右边等的信息:这个方法返回一个矩形对象,包含四个属性:left、top、right和bottom。分别表示元素各边与页面上边和左边
rect  = {
  bottom : 567,
  height:30,
  left:67.5,
  right:307.5,
  top:537,
  width:240
}

5. 功能优化 - 详情页的随机播放按钮

这里饿随机播放按钮功能就很好做了。在以前的基础上进行修改

src/components/music-list/music-list.vue 这个功能在list组件里

      // 给按钮绑定随机播放事件
      // 拿到当前的歌曲列表然后调用 action去提交全局状态
      random () {
        this.randomPlay(this.songs)
      }

vuex

/**
 * 随机播放歌曲
 * @param commit
 * @param state
 * @param list
 */
export const randomPlay = function ({commit, state}, list) {
  // 与顺序播放不同的是,播放模式 和 播放列表不同
  commit(types.SET_SEQUENCE_LIST, list)
  commit(types.SET_PLAYLIST, shuffle(list))
  commit(types.SET_CURRENT_INDEX, 0)  // 播放列表打乱之后,直接播放列表中的第一首歌曲就是
  commit(types.SET_MODE, playMode.random)
  commit(types.SET_FULL_SCREEN, true)
  commit(types.SET_PLAYING, true)
}

这里注意的是: 在之前我就觉得 shuffle 的功能有问题,这里发现了,修改了原来的播放列表。下面是修复代码

主要使用了 js 数组的 arr.slice() 方法返回一个新的副本

export function shuffle (arr) {
  let _arr = arr.slice() // 返回一个新的数组,包含从 start 到 end (不包括该元素)的 arrayObject 中的元素。
  let length = _arr.length
  for (let i = 0; i < length; i++) {
    let j = getRandomInt(0, i)
    let a1 = _arr[j]
    _arr[j] = _arr[i]
    _arr[i] = a1
  }
  return _arr
}

上面这个随机播放的功能是做好了。

但是又发现一个问题,切换播放模式的时候,当前播放的歌曲跟着变化了。这个是hi之前的代码

      playModelChange () {
        let mode = (this.mode + 1) % 3  // 循环3次切换
        this.setPlayModel(mode)
        let list = null
        if (mode === playMode.random) {
          // 获得原始列表,打乱顺序后,再赋值给播放列表
          list = shuffle(this.sequenceList)
        } else {
          list = this.sequenceList
        }

        this.setPlayList(list)
        this._resetCurrentIndex(list)
      },

---------------------

问题就出在这里的两行代码的顺序:
this.setPlayList(list)
this._resetCurrentIndex(list)

在src/store/getters.js中    
export const currentSong = (state) => {
  return state.playlist[state.currentIndex] || {}
}  

当前歌曲的详细信息是通过 索引和 播放列表获取的。上面的代码先更改了播放列表,引发了变更。导致getters返回的值变化了。


但是,把顺序交换一下,先设置index,为什么不会引发变更呢?

6. 功能优化 - 详情页的 点击歌曲播放 与 播放器模式相冲突

之前的 点击歌曲列表中的歌曲,会直接播放,是没有考虑到 播放模式,现在有播放模式了,功能肯定不正常了。

假如现在是 随机播放模式,再点击列表中的歌曲,会调用以前的顺序播放的代码

export const selectPlay = function ({commit, state}, {list, index}) {
  // 先暂时播放列表和排序列表一致
  // 且 打开全屏播放器模式,并且设置为播放状态
  commit(types.SET_SEQUENCE_LIST, list)
  commit(types.SET_PLAYLIST, list)
  commit(types.SET_CURRENT_INDEX, index)
  commit(types.SET_FULL_SCREEN, true)
  commit(types.SET_PLAYING, true)
}

所以更改代码如下

export const selectPlay = function ({commit, state}, {list, index}) {
  // 先暂时播放列表和排序列表一致
  // 且 打开全屏播放器模式,并且设置为播放状态
  commit(types.SET_SEQUENCE_LIST, list)

  let playList = null
  let _index = index
  if (state.mode === playMode.random) {
    playList = shuffle(list)
    _index = findIndex(playList, list[index]) // 获取点击的歌曲信息,所在新的播放列表中的索引
  } else {
    playList = list
  }
  commit(types.SET_PLAYLIST, playList)
  commit(types.SET_CURRENT_INDEX, _index)
  commit(types.SET_FULL_SCREEN, true)
  commit(types.SET_PLAYING, true)
}

function findIndex (list, currentSong) {
  let songId = currentSong.id
  let index = list.findIndex((item) => {
    return item.id === songId
  })
  return index
}

原理:

  1. 获取当前点击歌曲在打乱后的列表中的 索引
  2. 和之前播放模式切换的时候,先提交索引,再提交列表就ok了
© All Rights Reserved            updated 2017-12-28 02:49:41

results matching ""

    No results matching ""