基于vue3和audio封装的简易音频播放器

样式如图所示

复制代码
<template>
  <div class="audio-player">
    <div class="player_top" flex-ac  flex-justify-between >
      <div class="fileName  genericTitle" fs-28 l-height-32 height-64 pr-42 flex-ac>
        <span class="text-line-2">{{ fileName }}</span>
      </div>
      <div class="play_btn">
        <div class="toPlay" width-60 height-60 v-if="!playStatus" @click="onPlay"></div>
        <div class="toStop" width-60 height-60 v-else @click="onPause"></div>
      </div>
    </div>
    <div class="player_time" fs-14 mt-13 mb-8 flex  flex-justify-between>
      <span class="play_audioCurrent" style="color:#1e62d9"> {{ transTime(audioCurrent) }} </span>
      <span class="play_audioDuration" style="color:#A5A5A5"> {{ transTime(audioDuration) }} </span>
    </div>
    <div class="play_progress" pl-13 pr-13>
       <el-slider v-model="playProgress" :show-tooltip="false" @input="onProgressChange" />
    </div>
  </div>
  <audio ref="audioRef" :src="url" @canplay="onCanplay" @timeupdate="updateProgress" @ended="playEnd" />
</template>

<script setup lang="ts">
import { ref, onBeforeMount, onUnmounted, onMounted, watch, nextTick } from 'vue';

const props = defineProps({
  // 音频地址
  url: {
    type: String,
    required: true
  },
  // 音频名称
  fileName: {
    type: String,
    required: false
  }
});
const emits = defineEmits(['play', 'timeupdate']);
watch(
  () => props.url,
  newVal => {
    if (newVal) {
      nextTick(() => {
        initAudio();
      });
    }
  }
);

const speedVisible = ref<boolean>(false); // 设置音频播放速度弹窗
const audioRef = ref<HTMLAudioElement | null>(null); // 音频标签对象
const activeSpeed = ref(1); // 音频播放速度
const audioDuration = ref(0); // 音频总时长
const audioCurrent = ref(0); // 音频当前播放时间
const audioVolume = ref(1); // 音频声音,范围 0-1
const playStatus = ref<boolean>(false); // 音频播放状态:true 播放,false 暂停
const playProgress = ref(0); // 音频播放进度

const initAudio = () => {
  if (audioRef.value) {
    audioRef.value.load();
    playStatus.value = false;
    playProgress.value = 0;
    audioRef.value.src = props.url;
  }
};

// 音频加载完毕的回调
const onCanplay = () => {
  audioDuration.value = audioRef?.value.duration || 0;
};
const onPlay = async () => {
  // 音频播放完后,重新播放
  if (transTime(audioCurrent.value) === transTime(audioDuration.value)) audioRef.value.currentTime = 0;
  await audioRef.value.play();
  playStatus.value = true;
  audioDuration.value = audioRef.value.duration;
  emits('play');
};
const onPause = () => {
  audioRef.value.pause();
  playStatus.value = false;
};
// const onChangeSpeed = (value: number) => {
//   activeSpeed.value = value;
//   // 设置倍速
//   audioRef.value.playbackRate = value;
//   speedVisible.value = false;
// };
// const onHandleSpeed = () => {
//   speedVisible.value = !speedVisible.value;
// };
// // 设置声音
// const onSetVolume = (value: number) => {
//   audioRef.value.volume = value;
//   audioVolume.value = value;
// };
// 音频播放时间换算
const transTime = (value: number) => {
  let time = '';
  let h = parseInt(String(value / 3600));
  value %= 3600;
  let m = parseInt(String(value / 60));
  let s = parseInt(String(value % 60));
  if (h > 0) {
    time = formatTime(h + ':' + m + ':' + s);
  } else {
    time = formatTime(m + ':' + s);
  }
  return time;
};
// 格式化时间显示,补零对齐
const formatTime = (value: string) => {
  let time = '';
  let s = value.split(':');
  let i = 0;
  for (; i < s.length - 1; i++) {
    time += s[i].length == 1 ? '0' + s[i] : s[i];
    time += ':';
  }
  time += s[i].length == 1 ? '0' + s[i] : s[i];

  return time;
};
const onTimeUpdate = () => {
  if (audioRef.value) {
    audioCurrent.value = audioRef.value.currentTime;
    const progressPercentage = (audioRef.value.currentTime / audioRef.value.duration) * 100;
    emits('timeupdate', {
      currentTime: audioCurrent.value,
      duration: audioDuration.value,
      progress: progressPercentage
    });
  }
};
const onProgressChange = (value: number) => {
  // if (!value) {
  //   return;
  // }
  console.log(value,'value');
  audioRef.value.currentTime = (value / 100) * audioDuration.value;
};
const updateProgress = (e) => {
  // console.log(e,'e');
  onTimeUpdate();
  const value = e.target.currentTime / e.target.duration;
  if (audioRef.value.play) {
    playProgress.value = value * 100;
    audioCurrent.value = audioRef.value.currentTime;
  }
};
const playEnd = () => {
  playStatus.value = false;
};
// onMounted(() => {
//   initAudio();
// });
onBeforeMount(() => {

});
onUnmounted(() => {
});
</script>

<style lang="scss" scoped>
.audio-player {
  width: 100%;
  height: 193px;
  background: #ffffff;
  border: 10px solid #c5e9ff;
  border-radius: 20px;
  padding: 26px 26px 0 26px;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  .player_top{
    width: 100%;
  }
  .player_time{
    width: 100%;
  }
  .play_progress{
    width: 100%;
    ::v-deep(.el-slider__runway){
      height: 10px;
      border-radius: 6px;
      background-color: #e5e5e5;
      .el-slider__bar{
        height: 10px;
        border-radius: 6px;
        background: linear-gradient(270deg,#53c0ff, #3870ff);
      }
      .el-slider__button-wrapper{
        top: -13px;
      }
      .el-slider__button{
        width: 26px;
        height: auto !important;
        aspect-ratio: 1 !important;
        background-color: #1E62D9;
        border: px2vw(4) solid #ffffff;
        box-shadow: 0px 3px 6px 0px rgba(0,0,0,0.16);
      }
    }
  }

  .play-icon {
    width: 60px;
    height: 60px;
    // margin-right: 7px;
    cursor: pointer;
  }

  .play-time {
  }

  .play-progress {
    width: 160px;
    height: 4px;
    background-color: #323547;
    box-shadow: inset 0px 1px 0px 0px #20222d;
    border-radius: 2px;
    margin-right: 16px;
    position: relative;
    .play-current-progress {
      height: 4px;
      background: #00e5ff;
      border-radius: 2px;
      position: absolute;
      top: 0;
      left: 0;
    }
  }

  .play-voice {
    width: 20px;
    height: 20px;
    margin-right: 14px;
    cursor: pointer;
  }

  .play-speed {
    cursor: pointer;
    color: #00e5ff;
  }
  .fileName {
  }
  .play_btn {
    cursor: pointer;
    .toPlay {
      background: url('@/assets/images/icon_play_audio@2x.png') no-repeat;
      background-size: 100% 100%;
      background-origin: border-box;
      background-clip: content-box;
      &:hover {
        background: url('@/assets/images/icon_play_audio_h@2x.png') no-repeat;
        background-size: 100% 100%;
        background-origin: border-box;
        background-clip: content-box;
      }
    }
    .toStop {
      background: url('@/assets/images/icon_stop_audio@2x.png') no-repeat;
      background-size: 100% 100%;
      background-origin: border-box;
      background-clip: content-box;
      &:hover {
        background: url('@/assets/images/icon_stop_audio_h@2x.png') no-repeat;
        background-size: 100% 100%;
        background-origin: border-box;
        background-clip: content-box;
      }
    }
  }
}
</style>

使用

复制代码
              <AudioPlayer
                :url="currentResource?.resourceUrl"
                :fileName="currentResource?.resourceName"
                @play="playMedia"
                @timeupdate="toUpdatePlayMediaTime"
              />


// 音视频触发播放
const playMedia = () => {
  // console.log(currentResource.value, 'playMedia开始播放');
};
// 音视频播放进度
const toUpdatePlayMediaTime = e => {
  if (e.progress > 85 && currentResource.value.completeStatus === 0) {
    // 音视频播放进度大于85则该资源标记为学习完成
  }
};
相关推荐
良木林1 小时前
JavaScript书写基础和基本数据类型
开发语言·前端·javascript
工业甲酰苯胺7 小时前
TypeScript枚举类型应用:前后端状态码映射的最简方案
javascript·typescript·状态模式
谢尔登11 小时前
【React Native】ScrollView 和 FlatList 组件
javascript·react native·react.js
然我12 小时前
面试官:如何判断元素是否出现过?我:三种哈希方法任你选
前端·javascript·算法
kk_stoper12 小时前
如何通过API查询实时能源期货价格
java·开发语言·javascript·数据结构·python·能源
晨枫阳12 小时前
前端VUE项目-day1
前端·javascript·vue.js
颜酱13 小时前
抽离ant-design后台的公共查询设置
前端·javascript·ant design
绅士玖13 小时前
JavaScript 设计模式之单例模式🚀
前端·javascript·设计模式
Dream耀13 小时前
useReducer:React界的"灭霸手套",一个dispatch搞定所有状态乱局
前端·javascript·react.js
余大侠在劈柴13 小时前
pdf.js 开发指南:在 Web 项目中集成 PDF 预览功能
前端·javascript·学习·pdf