手写一个音频控件

52 阅读2分钟

记一次需求ui小姐姐要求音频控件的样式要求如下图中的样式

其中上面控件就是ui需要完成的样子,而下面的则是音频原样式,

image.png

原本想强制更改audio标签的样式,后来发现更改起来并非想象中的那么容易,因为上下俩之间的差异还是比较大的,所以便想到了自己i手写一个,后续发现vantui也有进度条的组件,所以就基于vantui的slider组件进行开发,可是在开发的过程发现几个问题,

问题一:音频总时长和slide组件的总进度匹配,slider组件有max属性可双向数据绑定,而音频总时长则可以通过udio标签loadedmetadata方法之后再经过ref获取标签的方式来获取音频来获取时长对应代码
   
        <van-slider
        inactive-color="rgba(4,115,255,0.1)"
        v-model="value"
        bar-height="5px"
        :max="audioDuration"
        @change="change">
        <template #button>
          <div class="custom-button"></div>
        </template>
      </van-slider>
    <audio
      v-show="false"
      controls
      :src="videoSrc"
      ref="toplay"
      @loadedmetadata="getDuration"
      @ended="handleAudioEnded"></audio>
      
      
      
     getDuration() {
      const ad = this.$refs.toplay;
      this.audioDuration = ad.duration;
      this.endTime = this.formatTime(ad.duration);
     
    },
问题二:左右时间逻辑:左侧动态时间可以通过audio标签的 ad.ontimeupdate更新方式来获取音频的播放进度,而右侧则通过总时长formatTime()转化则可以得到
 if (!this.$isEmpty(this.audioDuration)) {
        ad.ontimeupdate = () => {
          this.value = ad.currentTime;
          if (this.value > this.audioDuration) this.value = 0;
          this.startTime = this.formatTime(ad.currentTime);
        };
   }
   
   
    formatTime(seconds) {
      const minutes = Math.floor(seconds / 60).toFixed(0);
      const remainingSeconds = (seconds % 60).toFixed(0);
      const minutesString = String(minutes).padStart(2, '0');
      const secondsString = String(remainingSeconds).padStart(2, '0');
      return `${minutesString}:${secondsString}`;
    },
问题三:播放图标变化:阿里或者ui提供图标,再通过自定义方法play(),以及audio标签中方法 ad.play()和 ad.pause()控制音频的播放和停止,且同时控制图标的切换
play() {
      const ad = this.$refs?.toplay;
      if (ad.paused) {
        ad.play();
        this.src = play;
        this.codeName = '停止';
      } else {
        ad.pause();
        this.src = stop;
        this.codeName = '播放';
      }
    },
问题四:拖拽进度条控制音频的播放位置,组件含有change方法,可以监听用户拖拽的位置,然后在通过audio标签的 ad.currentTime属性去控制音频的播放进度,
     change(value) {
      const ad = this.$refs.toplay;
      ad.currentTime = value;
    },
总结所有代码逻辑如下
<template>
  <div>
    <div class="flex-center-center audio">
      <img :src="src" alt="" srcset="" @click="play" class="m_l24 m_r16" />
      <span class="time m_r24">{{ startTime }}</span>
      <van-slider
        inactive-color="rgba(4,115,255,0.1)"
        v-model="value"
        bar-height="5px"
        :max="audioDuration"
        @change="change">
        <template #button>
          <div class="custom-button"></div>
        </template>
      </van-slider>
      <span class="time m_l24 m_r24">{{ endTime }}</span>
    </div>
    <audio
      v-show="false"
      controls
      :src="videoSrc"
      ref="toplay"
      @loadedmetadata="getDuration"
      @ended="handleAudioEnded"></audio>
  </div>
</template>

<script>
import play from '@/assets/newApp/play.png';
import stop from '@/assets/newApp/stop.png';
export default {
  props: {
    show: { type: Boolean, default: false },
    videoSrc: { type: String, default: '' },
  },
  data() {
    return {
      src: stop,
      codeName: '播放',
      audioDuration: null,
      startTime: '00:00',
      endTime: '00:00',
      value: 0,
    };
  },
  computed: {
    config() {
      return this.$store.state.config;
    },
  },

  methods: {
    getDuration() {
      const ad = this.$refs.toplay;
      this.audioDuration = ad.duration;
      this.endTime = this.formatTime(ad.duration);
      if (!this.$isEmpty(this.audioDuration)) {
        ad.ontimeupdate = () => {
          this.value = ad.currentTime;
          if (this.value > this.audioDuration) this.value = 0;
          this.startTime = this.formatTime(ad.currentTime);
        };
      }
    },
    formatTime(seconds) {
      const minutes = Math.floor(seconds / 60).toFixed(0);
      const remainingSeconds = (seconds % 60).toFixed(0);
      const minutesString = String(minutes).padStart(2, '0');
      const secondsString = String(remainingSeconds).padStart(2, '0');
      return `${minutesString}:${secondsString}`;
    },
    change(value) {
      console.log(value, 'value');
      const ad = this.$refs.toplay;
      ad.currentTime = value;
    },
    handleAudioEnded(val) {
      const ad = this.$refs?.toplay;
      if (val && ad) {
        ad.currentTime = 0; // 将音乐的当前时间设置为0,以重新播放
        this.codeName = '播放';
        this.src = stop;
      }
    },
    play() {
      const ad = this.$refs?.toplay;
      if (ad.paused) {
        ad.play();
        this.src = play;
        this.codeName = '停止';
      } else {
        ad.pause();
        this.src = stop;
        this.codeName = '播放';
      }
    },
  },
  watch: {
    show(val) {
      const ad = this.$refs?.toplay;
      if (!val && ad) {
        ad.currentTime = 0; // 将音乐的当前时间设置为0,以重新播放
        ad.pause();
        this.codeName = '播放';
        this.src = stop;
      }
    },
  },
};
</script>

<style lang="scss" scoped>
audio {
  width: 702px;
  height: 74px;
  background: #ffffff;
  border-radius: 37px;
  border: 1px solid #cde3ff;
  position: relative;
}
.audio {
  width: 702px;
  height: 74px;
  background: #ffffff;
  border-radius: 37px;
  border: 1px solid #cde3ff;
  img {
    width: 46px;
    height: 46px;
  }
  .custom-button {
    width: 18px;
    height: 18px;
    background: #0473ff;
    border-radius: 9px;
  }
  .time {
    min-width: 75px;
    height: 36px;
    font-size: 28px;
    color: #1c1c1e;
    line-height: 36px;
  }
  .m_r24 {
    margin-right: 24px;
  }
  .m_l24 {
    margin-left: 24px;
  }
  .m_r16 {
    margin-right: 16px;
  }
}
</style>