vue audio 播放器(帶歌詞滾動)

 這個是基於element 做的audio播放器彈窗,再打開的時候通過調用這個彈窗的open方法吧數據傳入 

如:

da

 歌詞格式應該與正式格式同,即:時間 歌詞

[00:00.00] 作曲 : 高進 [00:01.00] 作詞 : 高進

可以通過ref直接找到該節點;

 audio 主要有幾個 方法:

play:播放;pause:暫停  當前時間:currentTime;歌曲時間 :maxTime;preload:自動播放;speed:播放速度;muted:靜音;playing:播放

先簡易的控制就是給audio 加個ref  然後就可用 this.$refs.audio 來找到這個播放器,  播放 就是this.$refs.audio.play 等

 

<template>
  <div class="con">
    <el-dialog
      v-el-drag-dialog
      :visible.sync="dialogVisible"
      title="音頻播放"
      width="40%">
      <div v-loading="audio.waiting" style="text-align:center" @click="closeVol">
        <div style="text-align:center;font-size:20px;margin-bottom:10px">
          <span>歌曲名:{{ title }}</span>
        </div>
        <div v-if="lrcShow" :class="{rotating:audio.playing}" class="logo" @click="lrcShow = !lrcShow">
          <img :src="logoUrl" class="bg" alt="logo">
        </div>
        <!-- 歌詞 -->
        <div v-if="!lrcShow" ref="lrcDiv" class="lrc" @click="lrcShow = !lrcShow">
          <p style="height:80px"/>
          <span v-if="lrcList.length === 0">{{ lrc }}</span>
          <ul v-if="lrcList.length >=0">
            <li v-for="(v,index) in lrcList" :key="index" :class="{currLrc:index===lrcIndex}">{{ v }}</li>
          </ul>
        </div>
        <span slot="footer" class="dialog-footer">
          <div class="di main-wrap">
            <audio
              ref="audio"
              :src="songUrl"
              :preload="audio.preload"
              class="dn"
              @play="onPlay"
              @error="onError"
              @waiting="onWaiting"
              @pause="onPause"
              @timeupdate="onTimeupdate"
              @loadedmetadata="onLoadedmetadata"
            />
            <div class="slideCon">
              <el-slider v-show="!controlList.noProcess" v-model="sliderTime" :format-tooltip="formatProcessToolTip" class="slider" @change="changeCurrentTime"/>
            </div>
            <div class="playCon">
              <el-row>
                <el-col :span="12" style="text-align:left">
                  <span>{{ audio.currentTime | formatSecond }}</span>
                </el-col>
                <el-col :span="12" style="text-align:right">
                  <span>{{ audio.maxTime | formatSecond }}</span>
                </el-col>
              </el-row>
              <el-row>
                <el-col :span="2">
                  <span class="volume">
                    <i class="el-icon-ali-play" style="font-size:0px"/>
                  </span>
                </el-col>
                <el-col :span="20" style="text-align:center">
                  <span @click="startPlayOrPause">
                    <i v-if="!audio.playing" class="el-icon-ali-play"/>
                    <i v-if="audio.playing" class="el-icon-ali-pause"/>
                  </span>
                </el-col>
                <el-col :span="2">
                  <span class="volume" @click.stop="showVol">
                    <i class="el-icon-ali-volume" @click.stop="volumeShow = !volumeShow"/>
                    <div v-if="volumeShow" class="volSlideCon">
                      <el-slider v-show="!controlList.noVolume" v-model="sliderVolume" :format-tooltip="formatVolumeToolTip" vertical class="slider" @change="changeVolume" />
                    </div>
                  </span>
                </el-col>
              </el-row>
            </div>
          </div>
        </span>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import elDragDialog from '@/directive/el-dragDialog/index'
import imglogo from '@/assets/img/imgLogo.png'

function realFormatSecond(second) {
  const secondType = typeof second
  if (secondType === 'number' || secondType === 'string') {
    second = parseInt(second)
    const hours = Math.floor(second / 3600)
    second = second - hours * 3600
    const mimute = Math.floor(second / 60)
    second = second - mimute * 60
    return ('0' + mimute).slice(-2) + ':' + ('0' + second).slice(-2)
  } else {
    return '0:00:00'
  }
}
export default {
  name: 'AudioPlay',
  directives: { elDragDialog },
  filters: {
    formatSecond(second = 0) {
      return realFormatSecond(second)
    }
  },
  data: function() {
    return {
      dialogVisible: false,
      currentTime: 0,
      logoUrl: imglogo,
      songUrl: '',
      lrcObj: '', // 歌詞
      lrc: '', // 歌詞
      lrcList: [],
      lrcIndex: 0,
      title: '',
      audio: {
        currentTime: 0,
        maxTime: 0,
        playing: false,
        muted: false,
        speed: 1,
        waiting: true,
        preload: 'auto'
      },
      sliderTime: 0,
      volumeShow: false,
      sliderVolume: 100,
      lrcShow: true,
      controlList: {
        // 不顯示下載
        noDownload: true,
        // 不顯示靜音
        noMuted: true,
        // 不顯示音量條
        noVolume: false,
        // 不顯示進度條
        noProcess: false,
        // 只能播放一個
        onlyOnePlaying: false,
        // 不要快進按鈕
        noSpeed: true
      }
    }
  },
  watch: {
    dialogVisible(val) {
      if (val === false) {
        this.$refs.audio.pause()
      }
    }
  },
  methods: {
    closeVol() {
      this.volumeShow = false
    },
    showVol() {
      this.volumeShow = true
    },
    // 進度條toolTip
    formatProcessToolTip(index = 0) {
      index = parseInt(this.audio.maxTime / 100 * index)
      return '進度條: ' + realFormatSecond(index)
    },
    // 音量條toolTip
    formatVolumeToolTip(index) {
      return '音量條: ' + index
    },
    // 播放跳轉
    changeCurrentTime(index) {
      this.$refs.audio.currentTime = parseInt(index / 100 * this.audio.maxTime)
    },
    changeVolume(index) {
      console.log(index)
      this.$refs.audio.volume = index / 100
      this.sliderVolume = index
    },
    // 控制音頻的播放與暫停
    startPlayOrPause() {
      return this.audio.playing ? this.pause() : this.play()
    },
    // 播放音頻
    play() {
      this.$refs.audio.play()
    },
    // 暫停音頻
    pause() {
      this.$refs.audio.pause()
    },
    // 當音頻暫停
    onPause() {
      this.audio.playing = false
    },
    // handleClose(done) {
    //   this.$confirm('確認關閉?', {
    //     confirmButtonText: '確認',
    //     cancelButtonText: '取消',
    //     type: 'warning'
    //   })
    //     .then(_ => {
    //       this.$refs.audio.pause()
    //       done()
    //     })
    //     .catch(_ => {})
    // },
    open(data) {
      this.lrcList = []
      this.songUrl = data.url
      data.lrc = data.lrc.replace(/[\\r\\n]/g, '')
      if (data.lrc !== '') {
        this.lrcObj = this.handleLrc(data.lrc)
        console.log(this.lrcObj)
      } else {
        this.lrc = '暫無歌詞'
      }
      if (data.imgUrl) {
        this.logoUrl = data.imgUrl
      }
      this.title = data.name
      this.dialogVisible = true
    },
    handleLrc(v) {
      var lyrics = v.split('[')
      v.split('[').map(v => {
        this.lrcList.push(v.slice(9))
      })
      this.lrcList.shift() // / 截取時會多一個空的
      lyrics.shift()
      var b = []
      lyrics.map(v => {
        b.push('[' + v)
      })
      lyrics = b
      var lrcObj = []
      for (var i = 0; i < lyrics.length; i++) {
        var lyric = decodeURIComponent(lyrics[i])
        var timeReg = /\[\d*:\d*((\.|\:)\d*)*\]/g
        var timeRegExpArr = lyric.match(timeReg)
        if (!timeRegExpArr) continue
        var clause = lyric.replace(timeReg, '')
        for (var k = 0, h = timeRegExpArr.length; k < h; k++) {
          var t = timeRegExpArr[k]
          const min = Number(String(t.match(/\[\d*/i)).slice(1))
          const sec = Number(String(t.match(/\:\d*/i)).slice(1))
          var time = min * 60 + sec
          lrcObj.push({ 'T': time, 'V': clause })
        }
      }
      return lrcObj
    },
    onError() {
      this.audio.waiting = true
      this.$message.error('文件錯誤')
    },
    // 當音頻開始等待
    onWaiting(res) {
      console.log(res)
    },
    // 當音頻開始播放
    onPlay(res) {
      this.audio.playing = true
      this.audio.loading = false
      if (!this.controlList.onlyOnePlaying) {
        return
      }
      const target = res.target
      const audios = document.getElementsByTagName('audio');
      [...audios].forEach((item) => {
        if (item !== target) {
          item.pause()
        }
      })
    },
    // 當timeupdate事件大概每秒一次,用來更新音頻流的當前播放時間
    onTimeupdate(res) {
      this.audio.currentTime = res.target.currentTime
      this.sliderTime = parseInt(this.audio.currentTime / this.audio.maxTime * 100)
      const _this = this
      if (this.lrc !== '暫無歌詞') {
        _this.lrcObj.map((v, index) => {
          if (v.T === Math.floor(this.audio.currentTime)) {
            this.lrcIndex = index
          }
        })
        this.$refs.lrcDiv.scrollTop = 20 * this.lrcIndex
      }
    },
    // 當加載語音流元數據完成後,會觸發該事件的回調函數
    // 語音元數據主要是語音的長度之類的數據
    onLoadedmetadata(res) {
      this.audio.waiting = false
      this.audio.maxTime = parseInt(res.target.duration)
    }

  }
}
</script>
<style scoped>
.con>>>.el-dialog{
    background: #f1f3f4 !important;
}
.logo{
  width: 220px;
  margin: 0 auto;
  height: 220px;
  overflow: hidden;
  border-radius: 50%;
  background: url("../../assets/img/audiobg.jpg");
  position: relative;
  /* border: 1px solid #ccc; */
}
.logo .bg{
  width: 96px;
  height: 96px;
  display: block;
  border-radius: 50%;
  position: absolute;
  top: 50%;
  left: 50%;
  margin-left: -48px;
  margin-top: -48px;
}

.con>>>.el-dialog__body{
  padding: 0;
}
.con>>>.el-dialog .el-dialog__footer{
  height: 85px !important;
  text-align: center;
  padding: 0;
}

/* .el-dialog__footer button{
    margin-left: 56px;
} */
.audio{
  display: inline;
  border: 1px solid #d1d1d1;
  background-color: #f1f3f4;
  border-radius: 8px;
}

.main-wrap{
  padding: 10px 15px;
}
.slideCon{
  width: 300px;
  margin: -10px auto;
}
.playCon {
  width: 306px;
  padding-right: 4px;
  font-size: 12px;
  margin-top: 5px;
  color: #999;
}
.playCon i{
  font-size: 44px;
  color: #262c2f;
}
/* .slider {
  display: inline-block;
  width: 200px;
  position: relative;
  top: 14px;
  margin: 0 15px;
} */
.di {
  display: inline-block;
}
.slideCon>>>.el-slider__button-wrapper{
  height: 22px;
  width: 22px;
  top: -10px;
}
.slideCon>>>.el-slider__button{
  height: 8px;
  width: 8px;
  border:4px solid #262c2f;
}
.slideCon>>>.el-slider__runway{
  height: 2px;
}
.slideCon>>>.el-slider__bar{
  height: 2px;
  background: #262c2f
}

.dn{
  display: none;
}
.el-dialog__body .el-tag{
  margin: 0;
}
.time{
  color:#999;
  font-size: 12px;
  padding:4px;
}
.volume{
  display:block;
  float:right;
  padding-top: 20px;
  position: relative;
  z-index: 999;
}
.volume i{
  font-size: 20px
}
.volume .volSlideCon{
  position: absolute;
  bottom: 20px;
  left: -7px;
  height: 60px;
  width: 20px;
}
.volume .volSlideCon>>>.el-slider.is-vertical{
  height: 60px;
}
.volume .volSlideCon>>>.el-slider__button-wrapper{
  height: 22px;
  width: 22px;
  left: -10px;
}
.volume .volSlideCon>>>.el-slider__button{
  height: 8px;
  width: 8px;
  border:4px solid #262c2f;
}
.volume .volSlideCon>>>.el-slider__runway{
  width: 2px;
}
.volume .volSlideCon>>>.el-slider__bar{
  width: 2px;
  background: #262c2f
}
.lrc{
  height: 220px;
  width: 240px;
  margin: 0 auto;
  overflow-y: scroll;
}
.lrc span{
  color: #e65e5e
}
.lrc ul li{
  list-style: none;
  max-width: 220px;
  padding: 0 4px;
  line-height: 20px;
}
.currLrc{
  color: #e65e5e;
  font-size: 16px;
  transition: all 1s;
}
::-webkit-scrollbar
{
    width: 4px;
    height: 4px;
    background-color: #F5F5F5;
}

/*定義滾動條軌道 內陰影+圓角*/
::-webkit-scrollbar-track
{
    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
    border-radius: 4px;
    background-color: #F5F5F5;
}

/*定義滑塊 內陰影+圓角*/
::-webkit-scrollbar-thumb
{
    border-radius: 4px;
    -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
    background-color: #555;
}
</style>

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章