• vue-music 关于Player (播放器组件)--播放和进度条


    迷你播放器

    1.播放器组件会在各个页面的情况下会打开。 首先在vuex state.js 中定义全局的播放器状态

    import {playMode} from 'common/js/config.js';
    
    const state = {
        singer:{},    
        playing:false,    //是否播放
        fullScreen:false,    //是否全屏
        playList:[],   //播放列表
        sequenceList:[],    // 非顺序播放列表
        mode:playMode.sequence,   // 播放模式(顺序0,循环1,随机2)
        currentIndex:-1,  //当前播放索引
    }
    export default state        
    
    
    ---------------------------------------------
    // config.js
    
    export const playMode = {
        sequence:0,
        loop:1,
        random:2
    }

    2.进入播放器页面时获取播放列表数据,改变播放状态   在music-list列表中打开

    在song-list 组件中派发事件到父组件,传入当前歌曲的信息和索引

    <li @click="selectItem(song,index)" v-for="(song,index) in songs" class="item">
    
    ------------------------------
    selectItem(item,index){
    this.$emit('select',item,index)
    },

    在music-list 组件中接受派发事件。

    <song-list :rank="rank" :songs="songs" @select="selectItem"></song-list>

    3. 如果commit 多个状态在actions 里设置

    import {playMode} from 'common/js/config.js'
    
    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_STATE, true)
    }

    4. 在music-list 组件中 用mapActions提交 改变值

    import {mapActions} from 'vuex'
    
    methods:{
        selectItem(item,index){
          this.selectPlay({
            list:this.songs,
            index
          })
        },
        ...mapActions([
          'selectPlay'
        ])
      },

    5.在palyer 中获取vuex 全局状态,赋值状态到相应位置(代码为完整代码,对照后面讲解慢慢理解)

    <div class="player" v-show="playList.length>0">    // 如果有列表数据则显示
            <div class="normal-player" v-show="fullScreen">  //如果全屏
              <div class="background">
                <img :src="currentSong.image" alt="" width="100%" height="100%">    //模糊背景图
              </div>
                <div class="top">
                  <div class="back" @click="back">
                    <i class="icon-back"></i>
                  </div>
                  <h1 class="title" v-html="currentSong.name"></h1>    //当前歌曲名称
                  <h2 class="subtitle" v-html="currentSong.singer"></h2>  //当前歌手名
                </div>
                <div class="middle">
                  <div class="middle-l">
                    <div class="cd-wrapper">
                      <div class="cd" :class="cdCls">
                        <img :src="currentSong.image" alt="" class="image">    //封面图
                      </div>
                    </div>
                  </div>
                </div>
                <div class="bottom">
              <div class="progress-wrapper">
                <span class="time time-l">{{ format(currentTime) }}</span>
                <div class="progress-bar-wrapper">
                  <progress-bar :percent="percent" @percentChange="onProgressBarChange"></progress-bar>
                </div>
                <span class="time time-r">{{ format(currentSong.duration) }}</span>
              </div>
                     <div class="operators">
                    <div class="icon i-left">
                      <i :class="iconMode" @click="changeMode"></i>
                    </div>
                        <div class="icon i-left" :class="disableCls">
                            <i @click="prev" class="icon-prev"></i>
                          </div>
                          <div class="icon i-center" :class="disableCls">
                            <i :class="playIcon" @click="togglePlaying"></i>
                          </div>
                          <div class="icon i-right" :class="disableCls">
                            <i @click="next" class="icon-next"></i>
                          </div>
                          <div class="icon i-right">
                            <i class="icon icon-not-favorite"></i>
                          </div>
                    </div>
                 </div>
             </div>
             </transition>
             <transition name="mini">
                  <div class="mini-player" v-show="!fullScreen" @click="open">
                      <div class="icon">
                          <img :src="currentSong.image" alt="" width="40" height="40" :class="cdCls">
                      </div>
                      <div class="text">
                          <h2 class="name" v-html="currentSong.name"></h2>
                          <p class="desc" v-html="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>
              <audio :src="currentSong.url" ref="audio" @canplay="ready" @error="error" @timeupdate="updateTime" @ended="end"></audio>
      </div>

    打开播放器的状态

    import {mapGetters,mapMutations} from 'vuex';
    
    ...mapGetters([
        'fullScreen',
        'playList',
        'currentSong',
        'playing',
        'currentIndex',
    ])

    注意:不可在组件中直接赋值改版vuex 中的状态 this.fullScreen = false 需要通过mutations 改变,定义mutation-types 和mutations 然后 用vuex的 mapMutations 代理方法调用

    [types.SET_FULL_SCREEN](state, flag) {
        state.fullScreen = flag
      },
    
    import {mapGetters,mapMutations} from 'vuex';
    methods:{
        ...mapMutations({
        setFullScreen:"SET_FULL_SCREEN",    
        }),
        back(){
        this.setFullScreen(false)
        },
    }

    设置点击播放按钮方法

    <i :class="playIcon" @click="togglePlaying"></i>
    togglePlaying(){
        this.setPlayingState(!this.playing);    //改变全局变量playing 的属性
    },
    
    // 然后watch 监听playing 操作实际的audio 标签的播放暂停
    watch:{
          playing(newPlaying){
              let audio = this.$refs.audio;
              this.$nextTick(() => {
                  newPlaying ? audio.play():audio.pause();
              })
          }
      },
    
    // 用计算属性改变相应的播放暂停图标
    playIcon(){
        return this.playing? 'icon-pause':'icon-play'
    },

    设置点击播放上一首和下一首按钮方法。用mapGetters 获取currentIndex 的值(加一或减一) 并改变,从而改变 currentSong 的状态,监听切换播放。判断播放列表界限重置,

    prev(){
        if(!this.songReady){                    return;              }
        let index = this.currentIndex - 1;
        if(index === -1){    //判断播放列表界限重置
            index = this.playList.length-1;
        }
        this.setCurrentIndex(index);
        if(!this.playing){  //判断是否播放改变播放暂停的icon
            this.togglePlaying();
        }
        this.songReady = false;
    },
    next(){
        if(!this.songReady){    return;   }
        let index = this.currentIndex + 1;
        if(index === this.playList.length){    //判断播放列表界限重置
            index = 0;
        }
        this.setCurrentIndex(index);
        if(!this.playing){
            this.togglePlaying();
        }
        this.songReady = false;
    },

    监听audio 元素标签的canpaly 事件,当歌曲加载就绪 和 error 事件,当歌曲发生错误的时候,做用户体验,防止用户快速切换导致报错。

    设置songReady 标志位 如果歌曲没有准备就绪,点击下一首的时候直接return false

    data(){
        return {
            songReady:false,
        }
    },
    
    ready(){
        this.songReady = true;
    },
    error(){
        this.songReady = true;
    },

    进度条

    audio元素监听 timeupdate 事件获取当前播放时间的 可读写属性 时间戳。用formt做格式化时间处理,(_pad 为补零函数 )

    获取音频总时长 currentSong.duration 

    <div class="progress-wrapper">
      <span class="time time-l">{{ format(currentTime) }}</span>
      <div class="progress-bar-wrapper">
        <progress-bar :percent="percent" @percentChange="onProgressBarChange"></progress-bar>
      </div>
      <span class="time time-r">{{ format(currentSong.duration) }}</span>
    </div>
    
    <audio :src="currentSong.url" ref="audio" @canplay="ready" @error="error" @timeupdate="updateTime" @ended="end"></audio>
    updateTime(e){
      this.currentTime = e.target.currentTime;    // 获取当前播放时间段
    },
    
    format(interval){
      interval = interval | 0;
      const minute = interval/60 | 0;
      const second = this._pad(interval % 60);
      return `${minute}:${second}`;      
    },
    
    _pad(num,n=2){
      let len = num.toString().length;
      while(len<n){
        num = '0' + num;
        len ++;
      }
      return num;
    },

    建立progress-bar 组件 接收pencent 进度参数,设置进度条宽度和小球的位置。player组件 设置计算属性percent

    percent(){
      return this.currentTime / this.currentSong.duration            // 当前时长除以总时长
    },

    progress-bar 组件

    <div class="progress-bar" ref="progressBar" @click="progressClick">
        <div class="bar-inner">
          <div class="progress" ref="progress"></div>
          <div class="progress-btn-wrapper" ref="progressBtn"
                @touchstart.prevent="progressTouchStart"
                @touchmove.prevent="progressTouchMove"
                @touchend="progressTouchEnd"      
          >
            <div class="progress-btn"></div>
          </div>
        </div>
      </div>
    const progressBtnWidth = 16    //小球宽度
    
    props:{
      percent:{
        type:Number,
        default:0
      }
    },
    
    
    watch:{
      percent(newPercent){
        if(newPercent>=0 && !this.touch.initated){    
          const barWidth = this.$refs.progressBar.clientWidth - progressBtnWidth;
          const offsetWidth = newPercent * barWidth;
          this.$refs.progress.style.width = `${offsetWidth}px`;
          this.$refs.progressBtn.style.transform=`translate3d(${offsetWidth}px,0,0)`
        }
      }
    }

    设置拖动

    在进度条小按钮progressBtn 上添加touchstart,touchmove,touchend 事件监听方法,事件添加 prevent 防止拖动默认浏览器行为,获取拖动的信息进行计算

    在实例上创建一个touch 对象维护不同的回调之间的通讯共享状态信息。  touchstart事件方法中 ,首先设置this.touch.initated为true,表示拖动开始。  记录开始点击位置 e.touches[0].pageX 存到 touch 对象上,记录当前的进度宽度。

    在touchmove 中首先判断 是否先进入了 touchstart 方法,计算得到 移动的位置 减去 点击开始的位置的 偏移量长度。 let deltax = e.touches[0].pageX - this.touch.startX 

    就可以 设置进度条 已有的长度加上偏移量长度。最大不能超过父级progressbar 的宽度

    调用this._offset(offsetWidth) 方法设置进度条宽度

    在touchend 事件方法中将 this.touch.initated 设置为false,表示拖动结束,并派发事件到player 组件将audio的currentTime 值设置为正确值,参数为pencent

    在progressbar 中增加点击事件,调用this._offset(e.offsetX),并且派发事件

    created(){    this.touch = {};  },
    methods:{
      progressTouchStart(e){
        this.touch.initiated = true;
        this.touch.startX = e.touches[0].pageX;
        this.touch.left = this.$refs.progress.clientWidth;
      },
      progressTouchMove(e){
        if(!this.touch.initiated){
          return;
        }
        let deltaX = e.touches[0].pageX - this.touch.startX;
        let offsetWidth = Math.min(this.$refs.progressBar.clientWidth - progressBtnWidth,Math.max(0,this.touch.left + deltaX));
        this._offset(offsetWidth);
      },
      progressTouchEnd(e){
        this.touch.initiated = false;
        this._triggerPercent();
      },
      progressClick(e){
        const rect = this.$refs.progressBar.getBoundingClientRect();
        const offsetWidth = e.pageX - rect.left;
        this._offset(offsetWidth);
        // this._offset(e.offsetX);
        this._triggerPercent();
      },
      _offset(offsetWidth){
        this.$refs.progress.style.width = `${offsetWidth}px`;
        this.$refs.progressBtn.style[transform] = `translate3d(${offsetWidth}px,0,0)`;
      },
      _triggerPercent(){
        const barWidth = this.$refs.progressBar.clientWidth - progressBtnWidth;
        const percent = this.$refs.progress.clientWidth / barWidth;
        this.$emit("percentChange",percent)
      }
    },
  • 相关阅读:
    Redis学习小结
    抽屉模型
    用户提交数据的验证
    jsonp原理与实验
    文件上传
    项目
    CBV
    C++算法 线段树
    写一些奇怪的东西找到的奇怪的错误
    php安装过程出现的一些错误问题:
  • 原文地址:https://www.cnblogs.com/catbrother/p/9180348.html
Copyright © 2020-2023  润新知