vue中自定义一个音频组件

2,104 阅读3分钟

开发初衷:

项目需要,没在网上找到合适的,于是就手写了。功能点有:显示、播放上传的音频、更改上传音频的名字、设置音频上线的时间。

该组件的主要优点是:灵活性高,样式可以自己按照设计稿布局

使用规则:

  1. 引入audioItem组件
  2. 调用组件

先上图:

调用组件:

<template>
    <div>
        <audio-player ref="audioPlayer" :item="list[0]" @handleConfirm="getConfirm"></audio-player>
        <audio-player ref="audioPlayer" :item="list[1]" @handleConfirm="getConfirm"></audio-player>
    </div>
</template>
<script>
import AudioPlayer from './components/audioItem'
export default {
  data() {
    return {
      list: [
        {
          index: 0,
          title: '歌曲名称1',
          url: 'http://image.kaolafm.net/mz/audios/202003/c4a9f630-95bd-44d7-9874-3d004d7c5f25.mp3',
          input: '',
          date: '',
          curState: false
        },
        {
          index: 1,
          title: '歌曲名称2',
          url: 'http://audio.leting.io/45b32eea707d93f18bd23bc04c987f6d.mp3',
          input: '',
          date: '',
          curState: false
        }
      ]
    }
  },
  mounted() {
  },
  methods: {
    getConfirm(obj) {
      this.list.map(item => {
        //   若非当前obj音频播放,则让列表中的其他对象的状态为false,即:暂停其它音频播放
        if (item.index !== obj.index) {
          item.curState = false
        }
      })
    }
  },
  components: {
    AudioPlayer
  }
}
</script>
<style scoped>
    
</style>

组件代码:

// 组件audioItem.vue
<template>
  <div class="wrapper">
    <audio ref="audio">
      <source :src="item.url" type="audio/mp3" />
    </audio>
    <div class="top">
        <div class="control">
        <i class="play" :style="backgroundImage('audioplay/'+ (item.curState ? 'play' : 'pause') +'.svg')" :class="{ 'active' : item.curState }" @click="playHandle"></i>
        <div class="name" :title="name">{{ name }}</div>
        </div>
        <div class="time">
        <div class="time-line">
            <span class="left">{{ curTime }}</span>
            <div class="bar" ref="progressBarBg" @mousedown="handledown()">
            <div class="play-bar" ref="progressBar" :style="{ 'width' : width + '%'}"></div>
            <div class="play-point" ref="progressDot" :style="{ 'left' : width + '%'}"></div>
            <div class="play-bg"></div>
            </div>
            <span class="right">{{ duration }}</span>
        </div>
        </div>
    </div>
    <div class="bottom">
        <el-input class="bot-input"
        placeholder="请输入内容"
        v-model="item.input"
        clearable>
        </el-input>
        <div class="data-box">
            <el-date-picker
            v-model="item.date"
            type="datetime"
            placeholder="选择日期时间">
            </el-date-picker>
        </div>
    </div>
  </div>
</template>

<script>
import { backgroundImage } from '@/utils/index'
const getMmSsStr = (timestamp) => {
  const m = Math.floor(timestamp / 60)
  const s = Math.floor(timestamp % 60)
  return (m < 10 ? '0' + m : m) + ':' + (s < 10 ? '0' + s : s)
}

export default {
  name: 'AudioPlayer',
  props: {
    onEnd: {
      type: Function,
      default: () => {}
    },
    item: {
      type: Object,
      default: () => {
        return {
          url: '', // 音频地址
          title: '', // 音频名称
          input: '', // 输入框值
          date: '', // date选择器
          curState: false // 当前播放状态 未播放
        }
      }
    }
  },
  data() {
    return {
      slider: null,
      point: null,
      per: 0,
      curTime: '00:00',
      name: this.item.title,
      duration: '00:00',
      mp3: this.item.url,
      backgroundImage: backgroundImage
    }
  },
  watch: {
    item: {
      handler(val) {
        console.log(val.curState, 'watch------')
        if (val.url !== '') {
          this.mp3 = this.item.url
        }
        this.name = this.item.title
        try {
          if (!this.item.curState) {
            if (this.audio) {
              this.pause()
            }
          }
        } catch (err) {
          throw err
        }
      },
      deep: true,
      immediate: true
    },
    '$route': function() {
      this.pause()
      this.item.curState = false
    },
    'mp3': function() {
      this.pause()
      this.item.curState = false
    }
  },
  computed: {
    width() {
      return this.per
    }
  },
  mounted() {
    this.init()
    // this.dragEvent()
  },
  methods: {
    init() {
      const that = this
      this.audio = this.$refs.audio
      this.audio.src = this.item.url
      this.audio.load()

      // 暂停
      this.audio.addEventListener('pause', () => {
        // callback(this.audio.currentTime, this.audio.duration)
        that.$nextTick(() => {
          that.item.curState = false
        })
      })
      //  结束
      this.audio.addEventListener('ended', () => {
        that.$nextTick(() => {
          that.item.curState = false
        })
      })
      // 更新
      this.audio.addEventListener('timeupdate', () => {
        if (isNaN(that.audio.currentTime) || isNaN(that.audio.duration)) {
          return
        } else {
          that.$nextTick(() => {
            that.curTime = getMmSsStr(that.audio.currentTime)
            that.duration = getMmSsStr(that.audio.duration)
            that.per = that.audio.currentTime / that.audio.duration * 100
            console.log('timeUpDate...')
          })
        }
      })
      // 出错
      this.audio.addEventListener('error', () => {
        this.$message.error('音频加载出错...')
      })
      this.audio.addEventListener('canplaythrough', () => {
        // this.audio.play()
      })
    },
    play(mp3) {
      if (this.audio.src === mp3) {
        this.audio.play()
      } else {
        this.audio.src = mp3
        this.audio.load()
      }
    },
    pause() {
      this.audio.pause()
    },
    handledown() {
      // 只有音乐开始播放后才可以调节,已经播放过但暂停了的也可以
      console.log(this.audio.currentTime)
      if (!this.audio.paused || this.audio.currentTime !== 0) {
        const pgsWidth = this.$refs.progressBarBg.offsetWidth
        const rate = event.offsetX / pgsWidth
        this.audio.currentTime = this.audio.duration * rate
        this.updateProgress()
      }
    },
    // 更新进度条与当前播放时间
    updateProgress() {
      const value = this.audio.currentTime / this.audio.duration
      this.$refs.progressBar.style.width = value * 100 + '%'
      this.$refs.progressDot.style.left = value * 100 + '%'
      this.$nextTick(() => {
        this.curTime = getMmSsStr(this.audio.currentTime)
        this.duration = getMmSsStr(this.audio.duration)
        this.per = this.audio.currentTime / this.audio.duration * 100
      })
    },
    playHandle() {
      const _this = this
      console.log(_this.mp3, _this.item.curState, '0000000000')
      // 针对付费和免费的播放碎片处理
      _this.$emit('handleConfirm', _this.item)
      if (_this.item.curState) {
        console.log('走到暂停....')
        _this.pause()
        _this.$nextTick(() => {
          _this.item.curState = false
        })
      } else {
        console.log('走到播放....')
        _this.play(this.mp3)
        _this.$nextTick(() => {
          _this.item.curState = true
        })
      }
    }
  }
}
</script>

<style lang="scss" scoped>
.progress-bar-bg {
  width: 200px;
  margin-left: 14px;
  margin-right: 14px;
  background-color: #d9d9d9;
  position: relative;
  height: 2px;
  cursor: pointer;
}

.progress-bar {
  background-color: #649fec;
  width: 0;
  height: 2px;
}

.progress-bar-bg span {
  content: " ";
  width: 10px;
  height: 10px;
  border-radius: 50%;
  -moz-border-radius: 50%;
  -webkit-border-radius: 50%;
  background-color: #3e87e8;
  position: absolute;
  left: 0;
  top: 50%;
  margin-top: -5px;
  margin-left: -5px;
  cursor: pointer;
}
@mixin flexs($direction:row,$row: center,$column:center){
  display: flex;
  flex-direction: $direction;
  justify-content: $row;
  align-items: $column;
}
.wrapper {
  width: 600px;
  height: 120px;
  padding: 0 15px;
  box-sizing: border-box;
  background-color: rgba($color: #000000, $alpha: 0.35);
  border-radius: 4px;
  font-size: 14px;
  color: #ffffff;
  margin: 10px 0;

  .top{
     width:100%; 
     height:40px;
     display:flex;
     justify-content: space-between !important;
     align-items:center !important;

    .control {
    width: 250px;
    height: 25px;
    display:flex;
    justify-content: space-between;
    align-items:center;

    &>i {
      cursor: pointer;
      display: inline-block;
      background-size: contain;
      background-repeat: no-repeat;
    }
    .play {
      width: 25px;
      height: 25px;
    }
  }
  .time {
    width:300px;
    height: 25px;
    @include flexs(column, space-around , flex-start);
    // flex: 1;
    margin-left: 20px;
    .name {
      height: 22px;
      line-height: 22px;
      font-size: 12px;
      max-width: 240px;
      text-overflow: ellipsis;//让超出的用...实现
      white-space: nowrap;//禁止换行
      overflow: hidden;//超出的隐藏
    }
    .time-line {
      height: 40px;
      line-height: 40px;
      @include flexs(row, flex-start , center);
      &>span {
        font-size: 12px;
      }
      .bar {
        width: 200px;
        margin: 0 10px;
        margin-top: -4px;
        position: relative;
        &>div {
          position: absolute;
          top: 0;
        }
        .play-bar {
          width: 0px;
          height: 0;
          height: 4px;
          left: 0;
          top: 0;
          background: #F84E4E;
          z-index: 8;
        }
        .play-point {
          cursor: pointer;
          width: 10px;
          height: 10px;
          background: #fff;
          top: -3px;
          box-sizing: border-box;
          box-shadow: 0 0 4px 0 #F84E4E;
          border-radius: 50%;
          z-index: 9;
          left: 0;
        }
        .play-bg {
          width: 100%;
          height: 0;
          height: 4px;
          opacity: .9;
          background-color: #ffffff;
          z-index: 7;
          left: 0;
        }
      }
    }
  }
  }

  .bottom{
     width:100%;
     margin-top:16px;
     display:flex;
     justify-content:space-between;
     align-items:center;

     .bot-input{
         width:240px;
     }
     .date-box{
         width:100%;
         position:relative;
     }
  }
}
</style>