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

news2024/9/24 21:22:58

样式如图所示
在这里插入图片描述

<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则该资源标记为学习完成
  }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2073962.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

UE管理内容 —— FBX Morph Target Pipeline

目录 Naming Setting Up Morph Targets Importing Morph Targets Morph Target 是特定网格体的顶点位置的快照&#xff0c;该网格体在某种程度上已经变形&#xff1b;例如&#xff0c;可以使用一个角色模型&#xff0c;对其面部进行重塑以创建一个面部表情&#xff0c;然后将…

Linux 流式DMA映射(DMA Streaming Mapping)

流式DMA相关的接口为dma_map_sg(),dma_unmap_sg(),dma_map_single(),dma_unmap_single()。流式DMA一般用于已经分配好的内存&#xff0c;然后再对其进行DMA操作&#xff0c;而不是提前申请好一块cache一致性的内存给DMA用。例如从协议栈里发下来的一个包&#xff0c;想通过网卡…

day41| 01背包问题一 01背包问题二(滚动数组篇)416. 分割等和子集 1049.最后一块石头的重量II 494. 目标和 474. 一和零

文章目录 背景介绍01背包问题一思路方法一方法二01背包问题二(滚动数组篇)思路方法一方法二416. 分割等和子集思路方法一1049.最后一块石头的重量II思路方法一494. 目标和思路方法方法二 回溯法474. 一和零思路方法总结由于笔试的时候会判重,而这里面的代码都是我自己写的,…

不同场景下的负载均衡器

负载均衡主要用于分配来自互联网或局域网的请求或任务负载到多个服务器中。 这样做可以避免任何单个服务器的过载&#xff0c;提高响应速度&#xff0c;增加系统的整体处理能力&#xff0c;并确保系统的高可用性和可靠性。 负载均衡器大概可以分为 3 类&#xff0c;包括&#…

Argo/BGC-Argo数据下载

BGC-Argo官方网站 网址&#xff1a; https://biogeochemical-argo.org/data-access.php 信息&#xff1a;提供BGC-Argo位置及剖面预览 数据ftp下载&#xff1a;ftp://ftp.ifremer.fr/ifremer/argo 或者 ftp://usgodae.org/pub/outgoing/argo 问题&#xff1a;dac文件按照数据中…

Image Stride(内存图像行跨度)

When a video image is stored in memory, the memory buffer might contain extra padding bytes after each row of pixels. The padding bytes affect how the image is store in memory, but do not affect how the image is displayed. 当视频图像存储在内存时&#xff0…

EVE-NG安装部署使用

EVE-NG安装部署使用 一、EVE的虚拟化安装1、下载EVE-NG(社区版)2、导入虚拟机-配置-登录二、EVE中设备的连接sercureCRT连接wireshark连接一、EVE的虚拟化安装 1、下载EVE-NG(社区版) 官网下载地址(科学上网): https://www.eve-ng.net/index.php/download/ 中文网下载…

基于python django的图书数据分析系统,包括图书推荐和可视化大屏分析,带有后台

研究背景 随着数字化技术的发展&#xff0c;图书管理与数据分析在图书馆和在线图书销售平台中变得越来越重要。传统的图书管理方式通常只关注图书的借阅和归还&#xff0c;忽视了数据分析在图书管理中的潜力。通过对图书借阅、购买、和用户偏好等数据的分析&#xff0c;能够深入…

数论之组合数

组合数1&#xff1a; 预处理每一个组合数的大小 类似于dp&#xff0c;从a个苹果里面选b个出来&#xff1a;首先从a个苹果里面拿出来一个&#xff0c;这样就分成了两种&#xff0c;一种是包括这个拿出来的苹果的方案数&#xff0c;此时就只需要拿b-1个苹果。一种是不包括这种苹…

嵌入式笔记:半加器与全加器

一&#xff0c;门电路 本文使用digital软件中的双掷继电器来实现以下的门电路&#xff0c;并结合这些门电路实现半加器与八位全加器。 与门 当输入信号A&#xff0c;B都置高电平时&#xff0c;继电器带电具有磁性&#xff0c;将下方双刀开关吸附&#xff0c;使电路导通。在输出…

C++20中的约束与概念

类模板、函数模板和非模板函数(通常是类模板的成员)可能与约束(constraint)相关联&#xff0c;该约束指定对模板参数的要求(requirements)&#xff0c;可用于选择最合适的函数重载和模板特化。约束是使用模板时需要通过模板参数满足的条件或要求。这些要求的命名集合称为概念(c…

Ai+若依(系统接口--Swagger):04篇

Swagger&#xff0c;能够自动生成 API 的同步在线文档&#xff0c;并提供Web界面进行接口调用和测试。 可以直接去测试&#xff1a;--有的接口测试需要权限 我们可以去这样操作 F12 报错404 是因为多了个前缀 /dev-api 我们去后台删掉&#xff1a; 重启刷新&#xff1a;

day 39 代码随想录 | 打家劫舍 动态规划

198.打家劫舍 你是一个专业的小偷&#xff0c;计划偷窃沿街的房屋。每间房内都藏有一定的现金&#xff0c;影响你偷窃的唯一制约因素就是相邻的房屋装有相互连通的防盗系统&#xff0c;如果两间相邻的房屋在同一晚上被小偷闯入&#xff0c;系统会自动报警。 给定一个代表每个…

Ruby+Watir进行web UI自动化测试

1.新建工程文件 打开RubyMine&#xff0c;新建一个工程文件目录如下&#xff1a; login_mail.rb文件 # encoding:UTF-8 # frozen_string_literal: true当(/^打开谷歌浏览器&#xff0c;进入163邮箱登陆页面$/) do$driver Watir::Browser.new :chromesleep(2)$driver.window.…

裸机:串口通信

串口通信的基本原理 单工通信和双工通信 (1)单工就是单方向&#xff0c;双工就是双方同时收发&#xff0c;同时只能但方向但是方向可以改变叫半双工 (2)如果只能A发B收则单工&#xff0c;A发B收或者B发A收&#xff08;两个方向不能同时&#xff09;叫半双工&#xff0c;A发B收…

【C++】类与对象篇一

【C】类与对象篇一 一 .面向过程和面向对象初步认识二 .类的详解1.类的引入2.类的定义3.类的访问限定符及封装&#xff08;面试题&#xff09;4.类的作用域5.类的实例化6.类对象模型 三 . 结构体内存对齐规则&#xff08;面试题&#xff09;四 . this指针1.this指针的特性2.thi…

WOFOST的web应用

目录 运行可视化 参考链接 https://github.com/irripro/WOFOST_streamlit/tree/main 运行 运行主程序.py之后&#xff0c;得到一串命令 将该命令放到命令台中运行 然后会转到WOFOST的web端上 可视化 目前好像只支持欧洲区域的模拟运行

C++篇:C向C++迈进(上)

引言 C语言作为编程基石&#xff0c;其高效与直接性深受开发者喜爱。然而&#xff0c;随着软件复杂度的增加&#xff0c;C以其面向对象及高级特性成为了新的选择。我们接下来将学习C&#xff0c;从C语言迈向C。 什么是C C 是一种高级语言&#xff0c;由 Bjarne Stroustrup 于…

Docker 的简介

Docker 的简介 为什么会有 Docker环境一致性问题提高资源利用率和可移植性快速部署和伸缩简化管理和维护版本控制和回滚 Docker 的历史dotCloud 时代&#xff08;2010年前&#xff09;Docker 诞生&#xff08;2010-2013&#xff09;快速发展与开源&#xff08;2013-2014&#x…

mysql 日期字段自动填写日期 及自动更新日期

INSERT 时 自动给日期字段 添加 当前日期时间&#xff1a; 在默认里选中&#xff1a; CURRENT_TIMESTAMP UPDATE 时 自动给日期字段 更新 当前日期时间&#xff1a; 勾选&#xff1a;根据当前时间戳更新