• VUE练手项目实现音乐播放器(四)------- 播放控制组件


    2020.3.31 9:18 

    好的,早上好各位,今天我们来进行一个很炫酷的页面开发——播放器控制页面( srccomponentsPlay.vue ),如下图:

                                             

    完成目标:

    1、歌曲上半部分图片封面及隐藏页面按钮布局

    2、设计播放进度条

    3、歌词显示

    4、播放控制按钮

    我们可以看到,这些元素完美的结合在一起,整个页面有没有一种很高大上的感觉!好了,让我们来亲自动手实现它吧!

    1、 歌曲封面&隐藏按钮

    首先通过 getters 获取到歌曲的封面:

    computed: {
      imgUrl: function() {
        return this.$store.getters.coverImgUrl                //PlayService.js 中的 Getters
      },
    }

    目前的 HTML 结构如下:

    <div id="play" class="music-play-page">
      <div class="music-album">
        <div class="hide-icon">
          <img src="../assets/icon-jiantou.png">
        </div>
        <img v-lazy="imgUrl">
      </div>
     </div>

    此时设置 CSS:

    .music-play-page {
        width: 100%;
        max-width: 68vh;
        height: 100%;
        position: fixed;
        top: 0;
        z-index: 5;
      }

                                     

    你会发现,尽管根元素 设置了 100% ,但是歌曲的封面仍然只有一点,宽度根本没有填充到100%,上右图。没关系,只需要将 .music-album img 设置 100% 即可,如上左图。

    原因其实很简单,父元素 width 设置了 100%,但是子元素的内容并没有填充满100%,为了方便调试,我们在这种情况下,可以先设置 父元素 的背景颜色,比如蓝色,即可查看到父元素的 width 没有问题, 就会意识到 是 子元素 的 width 不够。

    下一步,我们来实现播放进度条,效果如图:

    实现起来很简单,关键点是我们需要一个 随歌曲的播放时间的更新,而不断更新的变量,作为 进度条指针移动 的参考, 我们将这个变量命名为: indicatorPosition,

    首先在 Vuex 里获取 currentTime(歌曲播放当前时间) 和 duration(歌曲时长),两个变量的比,就是 indicatorPosition:

    computed: {         //Vue计算属性
      ...mapState({
        indicatorPosition: state => state.PlayService.currentTime / state.PlayService.duration * 100,         
      }),
    }

    有了 indicatorPosition,我们就可以进行下一步,在 组件中 添加 html

    <div class="progress-bar-group">
    <div class="progress-bar"> //进度条及红色刻度 <div class="progress" :style="{indicatorPosition+'%'}"></div> //已播放部分进度条颜色加深 <div class="indicater" :style="{left:indicatorPosition+'%'}"></div> //红色刻度移动代码 </div> <div class="time-indicater"> <span>{{currentTime}}</span> //当前时间 <span>{{duration}}</span> //歌曲时长 </div> </div>

    由于QMplayer返回的时间是以秒为单位,我们需要在 PlayService.js 里将秒转化为 --分 : --秒的形式,Vuex 的 getters 就相当于 Vue 的 computed(计算属性)

    getters: {
        currentTime: state =>
        parseInt(state.currentTime / 60) + ':' + (Array(2).join(0) + (state.currentTime % 60)).slice(-2) //Array(2).join(0) 表示两个数组元素,以0作为间隔,所以其输出为 ‘0’
        ,
        duration: state =>
        parseInt(state.duration / 60) + ':' + (Array(2).join(0) + (state.duration % 60)).slice(-2),
    }

    为进度条部分添加 CSS :

    .progress-bar-group {
        height: 30px;
    }
      
    .progress-bar-group .progress-bar {
        height: 4px;
        background: #cccccc;
        position: relative;
    }
      
    .progress-bar-group .progress-bar .progress {
        height: 100%;
        background: #7f7f7f;
        /* 20%;*/
    }
      
    .progress-bar-group .progress-bar .indicater {
        position: absolute;
        width: 2px;
        height: 12px;
        background: #ff2d55;
        top: 0;
        /*left: 20%;*/
    }

    此时,我们的页面效果:

                                 

    是不是已经有模有样了呢,很好,下一步我们接着完成播放控制按钮。

     我们用列表元素,将五个 控制按钮陈列出来:

    <div class="music-ctrl">
      <ul>
        <li><img src="../assets/icon-like.png"></li>
        <li><img src="../assets/icon-shangyiqu.png"></li>
        <li><img :src="playing?$parent.iconPause:$parent.iconPlay"></li>
        <li><img src="../assets/icon-xiayiqu.png"></li>
        <li><img src="../assets/icon-list.png"></li>
      </ul>
    </div>

    重点 CSS :

    .music-ctrl ul li {
        float: left;        /*将按钮排列成一行*/
        width: 20%;         /*设置每个 按钮 宽度均等*/
        height: 100%;
      }
    
    .music-ctrl ul li img {
        display: block;
        width: 35px;       /*设置每个按钮大小*/
        margin: 0 auto;    /*设置每个按钮相对父元素框 居中显示*/
      }

    为 播放上一曲 按钮添加 事件点击:

    <li><img src="../assets/icon-shangyiqu.png"
             @touchend.prevent="playFront"
             @click="playFront">
    </
    li>

    PlayService.js 添加 mutations :

    playFront (state) {
      state.index = (state.index - 1 + state.playList.length) % state.playList.length
      state.song = state.playList[state.index]
      player.play(state.song.mid);
    },

    好了,到这里我们的 播放界面基本完成,效果图:

                       

    还是很炫酷的。不过,还是少了歌词,要是再把歌词加上去,岂不是 perfect!感兴趣的同学,一起向下研究。

    歌词组件: srccomponentsLyric.vue ,两个 props:'currentTime', 'songid' ,来自于父组件( srccomponentsPlay.vue ):

    <div class="lyric">
      <lyric :songid="song.id" :currentTime="currentTime"></lyric>  <!--song、currentTIme 来自于PlayService.js 中的 state和getters-->
    </div>

    Lyric.vue中的 watch 属性,监听 songid,当 songid 发生变化时,通过 this.$store.dispatch('getLyric', id) 获取歌词,但是获取到的歌词是加过密的,而且还需要 utf_8解析,数据如下图:

    我们首先需要做的就是解密,解密文件: srcutilsase64.js ,方法 decode 为解密算法,其中涉及到字符串的方法 charAt(),indexOf(), fromCharCode() ,这里重点说一下其中的正则表达式:

    input = input.replace(/[^A-Za-z0-9+/=]/g, "");    //将input中 非字母、数字、'+'、'/'、'=' 替换为 空

    参考正则表达式资料: https://www.cnblogs.com/snandy/p/3662423.html,上面表达式属于 排除性组合字符组。

    .decode() 解密后,数据格式:

    但是离我们想要的 数组键值对的格式 还是有一定的差距的,我们需要一步一步解析:

     

    注意: 第三步的 return语句:

    return {[t[0]] : t[1]}
    

    t[0] 的外面还有一层 '[ ]',可以看到该返回值 是一个对象,在JS的语法中,对象的属性名可以是标识符,也可以是表达式。这里采用表达式命名属性名,根据ES6语法,必须将表达式放在中括号里。

    OK,歌词解析好了,只需要完成歌词与播放时间匹配就可以啦:

    computed: {
      currentLyric: function () {
        if (this.lyric !== null) {
          let that = this
          let pastLyric = []
          let i = 0
          Object.keys(this.lyric).forEach(function (key) {
            if (key.split(':')
              .reduce((a, b) =>
              parseInt(a) * 60 * 100 + b
                .split('.')
                .reduce((a, b) =>
                parseInt(a) * 100 + parseInt(b))) - 120 <=          that.currentTimeStamp) {     //歌词的时间点 与 当前播放时间比较
              if (that.lyric[key] !== '
    ') pastLyric.push(that.lyric[key])
            } else if (i <= 1 && that.lyric[key] !== '
    ') {
              pastLyric.push(that.lyric[key])
              i++
            }
          })
          return pastLyric.slice(pastLyric.length - 4, pastLyric.length - 1)                      //播放器显示最后三条歌词
        }
      },
      currentTimeStamp: function () {                
        let t = this.currentTime.split(':')                    //currentTime不断更新
        return (parseInt(t[0]) * 60 + parseInt(t[1])) * 100
      }
    },

    到这里,我们的播放界面就基本完工啦,自己试听一下,有没有一点炫酷的感觉呢,哈哈。

  • 相关阅读:
    HDU5367 思维map // 动态线段树
    CF500C New Year Book Reading
    窗口的星星
    【模板】扫描线
    [JLOI2014]松鼠的新家
    [USACO15DEC]最大流Max Flow
    The Lost House
    介绍 Seq2Seq 模型
    word2vec 和 glove 模型的区别
    关于 word2vec 如何工作的问题
  • 原文地址:https://www.cnblogs.com/Fcode-/p/12603125.html
Copyright © 2020-2023  润新知