封装简单的音频组件(vue3语法)

参考方法

https://blog.csdn.net/sun12356499514452248/article/details/128518711

组件代码

<template>
  <div class="ks-audio">
    <div class="audio-wrapper">
      <van-slider
        class="audio-slider"
        :activeColor="color"
        v-model="sliderValue"
        :max="totalDuration"
        @dragStart="isSeeking = true"
        @dragEnd="isSeeking = false"
        @change="change"
      />
      <div class="audio-number-area">
        <div class="audio-number">{{ format(sliderValue) }}</div>
        <div class="audio-number">{{ format(totalDuration) }}</div>
      </div>
    </div>
    <div class="audio-control-wrapper">
      <div class="audio-control" v-if="control" :style="{ backgroundColor: color }" @click="toggleRate(-15)">
        <span class="iconfont icon-kuaitui"></span>
      </div>
      <div class="audio-control" v-if="control" :style="{ backgroundColor: color }" @click="prevAudio">
        <span class="iconfont icon-shangyige"></span>
      </div>
      <div
        class="audio-control big-sign"
        :style="{ backgroundColor: color }"
        :class="{ audioLoading: loading }"
        @click="togglePlayState"
      >
        <span class="iconfont icon-jiazaizhong" v-show="loading"></span>
        <span class="iconfont icon-bofang5" v-show="!loading && isPaused"></span>
        <span class="iconfont icon-zanting1" v-show="!loading && !isPaused"></span>
      </div>
      <div class="audio-control" v-if="control" :style="{ backgroundColor: color }" @click="nextAudio">
        <span class="iconfont icon-xiayige"></span>
      </div>
      <div class="audio-control" v-if="control" :style="{ backgroundColor: color }" @click="toggleRate(15)">
        <span class="iconfont icon-kuaijin"></span>
      </div>
    </div>
  </div>
</template>

<script>
import { onBeforeUnmount, reactive, toRefs, watch } from 'vue';
import { showToast } from 'vant';
export default {
  name: 'KsAudio',
  props: {
    src: {
      //音频链接
      type: String,
      default: '',
    },
    autoplay: {
      //是否自动播放
      type: Boolean,
      default: false,
    },
    control: {
      //是否显示控件
      type: Boolean,
      default: false,
    },
    continue: {
      //播放完成后是否继续播放下一首,需定义@next事件
      type: Boolean,
      default: true,
    },
    color: {
      //主色调
      type: String,
      default: '#3366cc',
    },
  },
  setup(props, { emit }) {
    const state = reactive({
      audioElem: null,
      sliderValue: 0, //slider当前进度
      totalDuration: 0, //总时长(单位:s)
      isError: false, //音频是否加载出错,常见于src出错时导致音频加载出错
      loading: false, //是否处于读取状态
      isPaused: true, //是否处于暂停状态
      isSeeking: false, //是否处于拖动状态
    });

    initAudio();

    //监听音频地址更改
    watch(
      () => props.src,
      () => {
        state.sliderValue = 0;
        state.totalDuration = 0;
        state.isError = false;
        state.loading = true;

        state.audioElem.src = props.src;
        state.audioElem.play();
      },
    );

    // 初始化音频
    function initAudio() {
      state.audioElem = new Audio(); //创建音频
      state.audioElem.src = props.src;
      let readyFunc = 'canplay';
      let isIOS = !!navigator.userAgent.match(/\(i[^;]+;( U;)? CPU.+Mac OS X/); //ios手机终端
      if (isIOS) {
        //注:ios系统canplay方法不生效,使用durationchange方法
        readyFunc = 'durationchange';
      }
      state.audioElem.addEventListener(readyFunc, () => {
        state.isError = false;
        state.totalDuration = state.audioElem.duration || 0;
        state.audioElem.autoplay = props.autoplay;
      });
      //音频进度更新事件
      state.audioElem.addEventListener('timeupdate', () => {
        if (!state.isSeeking) {
          state.sliderValue = state.audioElem.currentTime;
        }
      });
      //音频播放事件
      state.audioElem.addEventListener('play', () => {
        state.isPaused = false;
        state.loading = false;
      });
      //音频暂停事件
      state.audioElem.addEventListener('pause', () => {
        state.isPaused = true;
      });
      //音频结束事件
      state.audioElem.addEventListener('ended', () => {
        if (props.continue) {
          nextAudio();
        } else {
          state.isPaused = true;
          state.sliderValue = 0;
        }
      });
      //音频完成更改进度事件
      state.audioElem.addEventListener('seeked', () => {
        state.isSeeking = false;
      });
      //播放出错事件
      state.audioElem.addEventListener('error', () => {
        state.isError = true;
        state.isPaused = true;
        showToast('此音频暂不支持播放');
      });
    }

    //上一曲
    function prevAudio() {
      emit('prev');
    }
    //下一曲
    function nextAudio() {
      emit('next');
    }
    //格式化时长
    function format(num) {
      return (
        '0'.repeat(2 - String(Math.floor(num / 60)).length) +
        Math.floor(num / 60) +
        ':' +
        '0'.repeat(2 - String(Math.floor(num % 60)).length) +
        Math.floor(num % 60)
      );
    }
    //播放/暂停操作
    function togglePlayState() {
      if (!state.isError) {
        if (state.audioElem.paused) {
          state.audioElem.play();
          state.loading = true;
        } else {
          state.audioElem.pause();
        }
      } else {
        showToast('此音频暂不支持播放');
      }
    }
    //完成拖动事件
    function change() {
      state.audioElem.currentTime = state.sliderValue;
    }
    //快进快退
    function toggleRate(rate) {
      if (state.isError) {
        return;
      }
      let finalRate = state.sliderValue + rate;
      state.audioElem.currentTime = finalRate;
    }

    onBeforeUnmount(() => {
      //页面销毁前,暂停当前audio实例的播放,防止退出页面后音频播放仍然发出声音
      state.audioElem.pause();
      state.audioElem = null;
    });

    return {
      prevAudio,
      nextAudio,
      togglePlayState,
      change,
      toggleRate,
      format,
      ...toRefs(state),
    };
  },
};
</script>

<style scoped lang="less">
.ks-audio {
  .audio-wrapper {
    :deep(.van-slider) {
      background: #ccc;
      height: 4px;
      border-radius: 8px;
      .van-slider__button {
        width: 16px;
        height: 16px;
      }
    }
    .audio-number-area {
      display: flex;
      justify-content: space-between;
      margin-top: 4px;
      .audio-number {
        font-size: 10px;
        line-height: 12px;
        color: #999;
      }
    }
  }
  .audio-control-wrapper {
    margin-top: 10px;
    display: flex;
    justify-content: center;
    align-items: center;
    .audio-control {
      border-radius: 50%;
      width: 28px;
      height: 28px;
      display: flex;
      align-items: center;
      justify-content: center;
      color: #fff;
      margin-right: 24px;
      .iconfont {
        font-size: 12px;
      }
      &:last-child {
        margin-right: 0;
      }
    }
    .big-sign {
      width: 40px;
      height: 40px;
      .iconfont {
        font-size: 18px;
      }
    }
    .audioLoading {
      animation: loading 2s;
      animation-iteration-count: infinite;
      animation-timing-function: linear;
    }
  }
}
@keyframes loading {
  to {
    transform: rotate(360deg);
  }
}
</style>

调用代码

<ks-audio
            ref="ksAudioRef"
            :src="nowAudio.aurl"
            control
            :continue="true"
            @prev="toggleAudio('prev')"
            @next="toggleAudio('next')"
          ></ks-audio>

//上下曲
    function toggleAudio(type) {
      let pointIndex = null;
      state.audioList.forEach((item, index) => {
        if (item.aid == state.nowAudio.aid) {
          if (type == 'prev') {
            if (index == 0) {
              pointIndex = state.audioList.length - 1;
            } else {
              pointIndex = index - 1;
            }
          } else if (type == 'next') {
            if (index == state.audioList.length - 1) {
              pointIndex = 0;
            } else {
              pointIndex = index + 1;
            }
          }
        }
      });
      selectAudio(state.audioList[pointIndex]);
    }
posted @ 2024-05-29 10:15  huihuihero  阅读(153)  评论(0编辑  收藏  举报