基于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
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue 2和Vue 3在组件封装方面有一些区别。下面是一些主要的区别: 1. 语法:Vue 2使用的是Options API,而Vue 3引入了Composition API。Options API基于选项对象,在一个对象中定义组件的选项,但随着组件变得复杂,选项对象可能会变得冗长。Composition API允许开发者通过函数的形式来组织组件的逻辑,使代码更易维护和重用。 2. 组件注册:在Vue 2中,我们使用Vue.component()全局注册组件或者通过components选项在局部注册组件。而在Vue 3中,全局注册的方法变成了app.component(),而且不再需要通过Vue实例来进行注册。 3. Props 的类型声明:在Vue 2中,我们需要使用props选项来声明组件的Props,并可以指定各个prop的类型、默认值等信息。而在Vue 3中,可以使用`<script setup>`语法来声明Props并给它指定类型。 4. 生命周期钩子:在Vue 2中,我们使用各种生命周期钩子函数来处理组件的生命周期事件。而在Vue 3中,Options API中的生命周期钩子函数仍然可用,但推荐使用Composition API中的函数式API来处理。 5. Transition/动画:Vue 2中有内置的transition和动画系统,可以通过<transition>和<transition-group>标签来实现。而在Vue 3中,这些功能被迁移到了单独的@vue/transition和@vue/animation库中,需要额外安装和引入。 需要注意的是,Vue 3在组件封装方面引入了更多的改进,并且提供了更强大和灵活的开发体验。但由于Vue 3相对于Vue 2来说还比较新,一些库和插件可能还没有完全适配Vue 3,因此在升级之前需要考虑项目的具体情况和可行性。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值