vue+element 实现音乐播放(已修正element slider自动滑动的bug)

最近项目里需要用到音乐播放器,但是h5自带audio标签样式太丑,于是便自己实现一个。
关于使用vue+element实现audio的文章有很多,这里不作赘述,直接上效果图和第一版源代码。

初始版本

边框样式在组件外面

<template>
  <div>
    <audio ref="audio" id="audio" src="../../assets/audio/audio.mp3"
           @pause="onPause"
           @play="onPlay"
           @timeupdate="onTimeupdate"
           @loadedmetadata="onLoadedmetadata">
      您的浏览器不支持audio标签
    </audio>
    <div class="custom-audio clearfix">
      <div class="audio-item play" @click="play" v-if="!audio.playing">
        <img src="../../assets/audio/play.png" width="20"/>
      </div>
      <div class="audio-item play" @click="pause" v-if="audio.playing">
        <img src="../../assets/audio/pause.png" width="20"/>
      </div>
      <div class="audio-item mute" @click="mute" v-if="!audio.muted">
        <img src="../../assets/audio/cancleMute.png" width="20"/>
      </div>
      <div class="audio-item mute" @click="cancelMute" v-if="audio.muted">
        <img src="../../assets/audio/mute.png" width="20"/>
      </div>
      <div class="audio-item play-time">{{ audio.currentTime | formatSecond}}</div>
      <div class="audio-item progress">
        <el-slider @change="progressChange" v-model="audio.currentTime" :format-tooltip="realFormatSecond"
                   :max="audio.maxTime"></el-slider>
      </div>
      <div class="audio-item total-time">{{ audio.maxTime | formatSecond}}</div>
    </div>
  </div>
</template>

<script>
  function realFormatSecond(second) {
    var secondType = typeof second;

    if (secondType === 'number' || secondType === 'string') {
      second = parseInt(second);

      var hours = Math.floor(second / 3600);
      second = second - hours * 3600;
      var mimute = Math.floor(second / 60);
      second = second - mimute * 60;

      return ('0' + mimute).slice(-2) + ':' + ('0' + second).slice(-2)
    } else {
      return '00:00'
    }
  }

  export default {
    name: "CustomAudio",
    data() {
      return {
        value: 20,
        audio: {
          // 播放状态
          playing: false,
          // 静音状态
          muted: false,
          // 音频当前播放时长
          currentTime: 0,
          // 音量
          volume: 1,
          // 音频最大播放时长
          maxTime: 0
        },
        cacheCurrent: 0,
        cacheVoice: 1,
      }
    },
    methods: {
      // 音频相关方法
      // 播放音频
      play() {
        this.$refs.audio.play()
      },
      // 暂停音频
      pause() {
        this.$refs.audio.pause()
      },
      // 当音频播放
      onPlay() {
        this.audio.playing = true
      },
      // 当音频暂停
      onPause() {
        this.audio.playing = false
      },
      // 静音
      mute() {
        this.audio.volume = 0;
        this.audio.muted = true;
        this.$refs.audio.muted = true;
      },
      // 取消静音
      cancelMute() {
        this.audio.muted = false;
        this.$refs.audio.muted = false;
      },
      // 拖动进度滚动条
      progressChange() {
        console.log('拖动滚动条触发', this.cacheCurrent);
        this.$refs.audio.currentTime = this.cacheCurrent;
        this.audio.currentTime = this.cacheCurrent
      },
      // 当timeupdate事件大概每秒一次,用来更新音频流的当前播放时间
      onTimeupdate(res) {
        this.audio.currentTime = res.target.currentTime
      },
      // 获取音频长度
      onLoadedmetadata(res) {
        this.audio.maxTime = parseInt(res.target.duration)
      },
      realFormatSecond(second) {
        console.log('自动播放触发', second);
        this.cacheCurrent = second;
        return realFormatSecond(second);
      },
    },
    filters: {
      // 将整数转化成时分秒
      formatSecond(second = 0) {
        return realFormatSecond(second)
      }
    }
  }

</script>

<style scoped lang="scss">
  .custom-audio {
    line-height: 68px;
    border: 1px solid #6779ff;
    padding: 0 15px;
    text-align: center;

    .audio-item {
      float: left;

      img {
        vertical-align: middle;
      }

      &.play {
        width: 50px;
        cursor: pointer;
      }

      &.mute {
        width: 20px;
        margin-left: 8px;
        cursor: pointer;
        line-height: 30px;
        margin-top: 19px;
      }

      &.play-time {
        width: 80px;
      }

      &.progress {
        width: calc(100% - 238px);
        padding-top: 16px;
      }

      &.total-time {
        width: 80px;
      }
    }
  }
</style>


复制粘贴就完事了。
附赠几张用到的图片。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
到这里大体结构就已经出来,播放暂停静音功能可以正常使用。

一个小bug

不过拖动进度条会出现一个bug,博主这里用的element的版本是2.12.0,若是后面版本的element的slider滑块组件解决了这个bug,则可以跳过直达后面修改音量。
这个bug是什么呢?
在使用中可以很轻松的触发,当拖动进度停住鼠标延迟大概一秒左右松开,进度会回到原来的位置,而快速拖动和点击进度条则不会出现这个问题,我在第一版代码里打印了日志,截图如下:
在这里插入图片描述
拖动滚动条延迟一小会松开,自动播放绑定的值又会变化一次,导致最后一次变化的值覆盖了原来的值,即使已经在format-tooltip事件使用缓存的变量(有文章说这样可以获得正确的值,其实并没有用),使用官方的input方法也是一样。
那既然是倒数第二个值正确,那用数组把变化的值存起来,取倒数第二个值呢?
也不行,因为快速拖动和点击的话是最后一个值正确,这里就非常的蛋疼了,我就翻各种介绍实现audio的文章、帖子,大多数没有提到这个bug,直到我看到有篇文章写,在拖动的时候暂停然后松开再播放,这样就规避的这个bug,文章地址:https://www.cnblogs.com/522040-m/p/9837932.html
就在我也以为只能这样的时候,不经意间在监听鼠标抬起事件的时候,打印了我缓存的值,意外惊喜出现了:
在这里插入图片描述
在鼠标抬起的时候居然打印到了最后的值,虽然后面又触发了change,但是只要鼠标抬起的时候手动触发一下change方法,就可以完美解决,代码如下:

// html修改
<el-slider @change="progressChange" @mouseup.native="mouseupChangeTime" v-model="audio.currentTime" :format-tooltip="realFormatSecond"
                   :max="audio.maxTime"></el-slider>
// 方法修改
// 鼠标抬起改变当前时间点
mouseupChangeTime() {
  this.progressChange(this.cacheCurrent)
},
// 拖动进度滚动条
progressChange(value) {
  this.$refs.audio.currentTime = value >= 0 ? value : this.cacheCurrent;
  this.audio.currentTime = value >= 0 ? value : this.cacheCurrent
},

由于change方法会默认传一个对象,所以用value>=0来判断一下是否从使用传入的值,这样无论是怎样拖动或是点击都可以正常使用。

加个音量调节器

音量调节器也是用slider实现的,外面套一层弹出层,代码:

// html 部分 使用el-tooltip把原来的两张声音图片包住
<el-tooltip class="item" effect="light" placement="top">
  <div slot="content">
    <el-slider vertical height="120px" :step="0.01" :max="1" v-model="audio.volume" @input="voiceChange()"
               :format-tooltip="handelVoice"></el-slider>
  </div>
  <div class="audio-item mute" @click="mute" v-if="!audio.muted">
    <img src="../../assets/audio/cancleMute.png" width="20"/></div>
  <div class="audio-item mute" @click="cancelMute" v-if="audio.muted">
    <img src="../../assets/audio/mute.png" width="20"/></div>
</el-tooltip>
// js方法部分
// 拖动音量滚动条
voiceChange() {
  this.$refs.audio.volume = this.audio.volume;
},
// 处理音量显示
handelVoice() {
  return parseInt(this.audio.volume.toFixed(2) * 100);
}

这里的slider没有自动变化的情况,正常使用即可。
优化一下:

  1. 当声音调整为0的时候,置为静音
  2. 静音的时候调节声音改为取消静音
  3. 点击取消静音的时候回到原来的音量
 // 拖动音量滚动条
voiceChange() {
  this.cancelMute(false);
  if (this.audio.volume === 0) {
    this.mute(false)
  }
  this.$refs.audio.volume = this.audio.volume;
},
// 静音
mute(event) {
  // 正常点击静音和取消静音的时候把当前音量存下来,拖动触发的静音方法只需要改变状态
  event && (this.cacheVoice = this.audio.volume);
  this.audio.volume = 0;
  this.audio.muted = true;
  this.$refs.audio.muted = true;
},
// 取消静音
cancelMute(event) {
  event && (this.audio.volume = this.cacheVoice);
  this.audio.muted = false;
  this.$refs.audio.muted = false;
},

到这里基本功能已经实现,如需更多功能,自行添加一下即可,难点主要在slider的滑动那里,但是解决方案也异常简单,只是不太容易想到。
如果文章帮助到了您,还请帮忙点个赞!(o)/

最终版本

在这里插入图片描述

<template>
  <div>
    <audio ref="audio" id="audio" src="../../assets/audio/audio.mp3"
           @pause="onPause"
           @play="onPlay"
           @timeupdate="onTimeupdate"
           @loadedmetadata="onLoadedmetadata">
      您的浏览器不支持audio标签
    </audio>
    <div class="custom-audio clearfix">
      <div class="audio-item play" @click="play" v-if="!audio.playing">
        <img src="../../assets/audio/play.png" width="20"/></div>
      <div class="audio-item play" @click="pause" v-if="audio.playing">
        <img src="../../assets/audio/pause.png" width="20"/></div>
      <el-tooltip class="item" effect="light" placement="top">
        <div slot="content">
          <el-slider vertical height="120px" :step="0.01" :max="1" v-model="audio.volume" @input="voiceChange()"
                     :format-tooltip="handelVoice"></el-slider>
        </div>
        <div class="audio-item mute" @click="mute" v-if="!audio.muted">
          <img src="../../assets/audio/cancleMute.png" width="20"/></div>
        <div class="audio-item mute" @click="cancelMute" v-if="audio.muted">
          <img src="../../assets/audio/mute.png" width="20"/></div>
      </el-tooltip>
      <div class="audio-item play-time">{{ audio.currentTime | formatSecond}}</div>
      <div class="audio-item progress">
        <el-slider @change="progressChange($event)" @mouseup.native="mouseupChangeTime"
                   v-model="audio.currentTime" :format-tooltip="realFormatSecond" :max="audio.maxTime"></el-slider>
      </div>
      <div class="audio-item total-time">{{ audio.maxTime | formatSecond}}</div>
    </div>
  </div>
</template>

<script>
  function realFormatSecond(second) {
    var secondType = typeof second;

    if (secondType === 'number' || secondType === 'string') {
      second = parseInt(second);

      var hours = Math.floor(second / 3600);
      second = second - hours * 3600;
      var mimute = Math.floor(second / 60);
      second = second - mimute * 60;

      return ('0' + mimute).slice(-2) + ':' + ('0' + second).slice(-2)
    } else {
      return '00:00'
    }
  }

  export default {
    name: "CustomAudio",
    data() {
      return {
        value: 20,
        audio: {
          // 播放状态
          playing: false,
          // 静音状态
          muted: false,
          // 音频当前播放时长
          currentTime: 0,
          // 音量
          volume: 1,
          // 音频最大播放时长
          maxTime: 0
        },
        cacheCurrent: 0,
        cacheVoice: 1,
      }
    },
    methods: {
      // 音频相关方法
      // 播放音频
      play() {
        this.$refs.audio.play()
      },
      // 暂停音频
      pause() {
        this.$refs.audio.pause()
      },
      // 当音频播放
      onPlay() {
        this.audio.playing = true
      },
      // 当音频暂停
      onPause() {
        this.audio.playing = false
      },
      // 拖动音量滚动条
      voiceChange() {
        this.cancelMute(false);
        if (this.audio.volume === 0) {
          this.mute(false)
        }
        this.$refs.audio.volume = this.audio.volume;
      },
      // 静音
      mute(event) {
        event && (this.cacheVoice = this.audio.volume);
        this.audio.volume = 0;
        this.audio.muted = true;
        this.$refs.audio.muted = true;
      },
      // 取消静音
      cancelMute(event) {
        event && (this.audio.volume = this.cacheVoice);
        this.audio.muted = false;
        this.$refs.audio.muted = false;
      },
      // 鼠标抬起改变当前时间点
      mouseupChangeTime() {
        this.progressChange(this.cacheCurrent)
      },
      // 拖动进度滚动条
      progressChange(value) {
        this.$refs.audio.currentTime = value >= 0 ? value : this.cacheCurrent;
        this.audio.currentTime = value >= 0 ? value : this.cacheCurrent
      },
      // 当timeupdate事件大概每秒一次,用来更新音频流的当前播放时间
      onTimeupdate(res) {
        this.audio.currentTime = res.target.currentTime
      },
      // 获取音频长度
      onLoadedmetadata(res) {
        this.audio.maxTime = parseInt(res.target.duration)
      },
      realFormatSecond(second) {
        this.cacheCurrent = second;
        return realFormatSecond(second);
      },
      // 处理音量显示
      handelVoice() {
        return parseInt(this.audio.volume.toFixed(2) * 100);
      }
    },
    filters: {
      // 将整数转化成时分秒
      formatSecond(second = 0) {
        return realFormatSecond(second)
      }
    }
  }

</script>

<style scoped lang="scss">
  .custom-audio {
    line-height: 68px;
    border: 1px solid #6779ff;
    padding: 0 15px;
    text-align: center;

    .audio-item {
      float: left;

      img {
        vertical-align: middle;
      }

      &.play {
        width: 50px;
        cursor: pointer;
      }

      &.mute {
        width: 20px;
        margin-left: 8px;
        cursor: pointer;
        line-height: 30px;
        margin-top: 19px;
      }

      &.play-time {
        width: 80px;
      }

      &.progress {
        width: calc(100% - 238px);
        padding-top: 31px;
        >>> .el-slider__runway {
          margin: 0;
        }
      }

      &.total-time {
        width: 80px;
      }
    }
  }
</style>

  • 5
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值