vue-music 關於Player (播放器組件)--播放和進度條

迷你播放器html

1.播放器組件會在各個頁面的狀況下會打開。 首先在vuex state.js 中定義全局的播放器狀態vue

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列表中打開vuex

在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 組件中接受派發事件。app

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

 

3. 若是commit 多個狀態在actions 裏設置dom

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 全局狀態,賦值狀態到相應位置(代碼爲完整代碼,對照後面講解慢慢理解)oop

<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>

 

打開播放器的狀態this

import {mapGetters,mapMutations} from 'vuex';

...mapGetters([
    'fullScreen',
    'playList',
    'currentSong',
    'playing',
    'currentIndex',
])

 

注意:不可在組件中直接賦值改版vuex 中的狀態 this.fullScreen = false 須要經過mutations 改變,定義mutation-types 和mutations 而後 用vuex的 mapMutations 代理方法調用url

[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)
  }
},
相關文章
相關標籤/搜索