基于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则该资源标记为学习完成
  }
};
相关推荐
ggdpzhk2 小时前
VUE:基于MVVN的前端js框架
前端·javascript·vue.js
runing_an_min2 小时前
ffmpeg视频滤镜:提取缩略图-framestep
ffmpeg·音视频·framestep
小曲曲3 小时前
接口上传视频和oss直传视频到阿里云组件
javascript·阿里云·音视频
学不会•4 小时前
css数据不固定情况下,循环加不同背景颜色
前端·javascript·html
EasyNTS5 小时前
H.264/H.265播放器EasyPlayer.js视频流媒体播放器关于websocket1006的异常断连
javascript·h.265·h.264
安静读书5 小时前
Python解析视频FPS(帧率)、分辨率信息
python·opencv·音视频
佑华硬盘拷贝机5 小时前
音频档案批量拷贝:专业SD拷贝机解决方案
音视频
EasyNVR5 小时前
NVR管理平台EasyNVR多个NVR同时管理:全方位安防监控视频融合云平台方案
安全·音视频·监控·视频监控
活宝小娜6 小时前
vue不刷新浏览器更新页面的方法
前端·javascript·vue.js
程序视点6 小时前
【Vue3新工具】Pinia.js:提升开发效率,更轻量、更高效的状态管理方案!
前端·javascript·vue.js·typescript·vue·ecmascript