Vue3音频组件开发与使用指南

Vue3音频组件开发与使用指南

前言

在现代Web应用中,音频播放功能越来越常见,特别是在娱乐和社交应用中。本文将详细介绍如何在Vue3项目中开发一个功能完整的音频播放组件,包括播放控制、进度条、时间显示以及多音频实例管理等功能。

效果

项目结构

复制代码
src/
├── components/
│   └── audio/
│       └── index.vue          # 音频组件
├── store/
│   └── modules/
│       └── audio.ts           # 音频状态管理
└── assets/
        └── audio/
            ├── play.png       # 播放按钮图标
            └── pause.png      # 暂停按钮图标

核心功能特性

  • ✅ 播放/暂停控制
  • ✅ 进度条拖拽调节
  • ✅ 时间显示(当前时间/总时长)
  • ✅ 播放速度调节
  • ✅ 降噪功能
  • ✅ 多音频实例管理
  • ✅ 禁用状态支持
  • ✅ 自定义样式

音频状态管理 (Pinia Store)

首先,我们使用Pinia来管理全局音频状态,确保同时只有一个音频在播放:

typescript 复制代码
// src/store/modules/audio.ts
import { defineStore } from 'pinia';

type AudioElement = HTMLAudioElement & { uid?: symbol };

export const useAudioStore = defineStore('audio', {
  state: () => ({
    instances: new Set<AudioElement>(),
    currentPlaying: null as AudioElement | null
  }),

  actions: {
    // 注册音频实例
    register(audio: AudioElement) {
      if (!audio.uid) {
        audio.uid = Symbol('audio-instance');
      }
      this.instances.add(audio);
    },

    // 注销音频实例
    unregister(audio: AudioElement) {
      this.instances.delete(audio);
    },

    // 暂停除当前音频外的所有音频
    pauseAllExcept(currentAudio: AudioElement) {
      this.instances.forEach(audio => {
        if (audio.uid !== currentAudio.uid && !audio.paused) {
          audio.pause();
          audio.dispatchEvent(new Event('force-pause'));
        }
      });
      this.currentPlaying = currentAudio;
    }
  }
});

状态管理设计思路

  1. 实例管理:使用Set存储所有音频实例,避免重复
  2. 唯一标识:为每个音频元素分配唯一的Symbol标识
  3. 互斥播放:确保同时只有一个音频在播放
  4. 事件通知:通过自定义事件通知组件状态变化

音频组件实现

组件Props定义

typescript 复制代码
const props = defineProps({
  url: String,                    // 音频URL
  speed: String,                  // 播放速度
  noiseSuppression: Boolean,      // 降噪开关
  disabled: Boolean,              // 禁用状态
  isbg: String,                   // 背景样式
  mock: {                         // 模拟模式
    type: Boolean,
    default: false,
  },
  playData: {                     // 播放数据
    type: Object,
    default: () => ({}),
  },
  res: {                          // 响应数据
    type: String,
    default: '',
  },
});

核心响应式数据

typescript 复制代码
const audioPlayer = ref<any>();     // 音频元素引用
const audioUrl = ref(props.url);    // 音频URL
const progress = ref(0);            // 播放进度
const timeCount = ref(0);           // 总时长(秒*10)
const startTime = ref('00:00');     // 当前时间
const endTime = ref('00:00');       // 总时长
const isPlay = ref(false);          // 播放状态

关键功能实现

1. 播放控制
typescript 复制代码
const playClick = () => {
  if (endTime.value == '00:00') return;
  if (!audioUrl.value || props.disabled || props.mock) return;

  // 暂停其他音频
  if (audioPlayer.value) {
    audioStore.pauseAllExcept(audioPlayer.value);
  }

  startOrStop();
};

const startOrStop = () => {
  isPlay.value = !isPlay.value;
  emit('playClick', { ...props.playData, isPlay: isPlay.value, res: props.res });

  if (isPlay.value) {
    audioPlayer.value?.play();
    // 启动进度更新定时器
    time.value = setInterval(() => {
      progress.value++;
      startTime.value = secondToTime(progress.value / 10);
    }, 100);
  } else {
    audioPlayer.value?.pause();
    clearInterval(time.value);
    clearInterval(time1.value);
  }
};
2. 进度条控制
typescript 复制代码
// 进度条拖拽中
const proChange = () => {
  if (isDown.value) {
    clearInterval(time.value);
    clearInterval(time1.value);
    audioPlayer.value.pause(progress.value / 10);
  }
};

// 进度条拖拽结束
const proUp = () => {
  isDown.value = false;
  audioPlayer.value.currentTime = progress.value / 10;
  audioPlayer.value.play();
  clearInterval(time.value);
  clearInterval(time1.value);
  isPlay.value = true;
  
  // 重新启动进度更新
  time1.value = setInterval(() => {
    progress.value++;
    startTime.value = secondToTime(progress.value / 10);
  }, 100);
};
3. 时间格式化
typescript 复制代码
const secondToTime = (val: number) => {
  if (!isFinite(val)) return '0:00';

  val = Math.round(val);
  const minutes = Math.floor(val / 60);
  const seconds = val % 60;
  const secondsStr = seconds < 10 ? `0${seconds}` : seconds;

  return `${minutes}:${secondsStr}`;
};
4. 生命周期管理
typescript 复制代码
onMounted(() => {
  if (audioPlayer.value) {
    // 注册到全局store
    audioStore.register(audioPlayer.value);
    
    // 监听外部暂停事件
    audioPlayer.value.addEventListener('force-pause', () => {
      isPlay.value = false;
      clearInterval(time.value);
      clearInterval(time1.value);
    });

    // 初始化音频时长
    if (audioUrl.value) {
      playAudio();
    }
  }
});

onUnmounted(() => {
  if (audioPlayer.value) {
    audioStore.unregister(audioPlayer.value);
  }
});

组件模板结构

vue 复制代码
<template>
  <div>
    <div :class="`audio-zj ${(disabled || mock) && 'audio-disabled'} ${isbg ? '' : 'isbg'}`">
      <div class="a1">
        <!-- 播放/暂停按钮 -->
        <el-image :src="playImg" v-show="isPlay" class="a-icon" @click="playClick"></el-image>
        <el-image :src="pauseImg" v-show="!isPlay" class="a-icon" @click="playClick"></el-image>
        
        <!-- 进度条和时间显示 -->
        <div class="t-bottom">
          <el-slider
            v-model="progress"
            :show-tooltip="false"
            :max="timeCount"
            :disabled="disabled || mock"
            class="jindu"
            @input="proChange"
            @change="proUp"
          />
          <span class="already-time">{{ startTime }}/</span>
          <span class="total-time">{{ endTime }}</span>
        </div>
      </div>
      
      <!-- 隐藏的音频元素 -->
      <audio
        ref="audioPlayer"
        class="audioPlayer"
        @timeupdate="timeupdate"
        :src="audioUrl"
        @loadeddata="onLoad"
        @ended="onEnded"
        controls
      ></audio>
    </div>
  </div>
</template>

使用方法

1. 基础使用

vue 复制代码
<template>
  <div>
    <Audio :url="audioUrl" />
  </div>
</template>

<script setup>
import Audio from '@/components/audio/index.vue';

const audioUrl = ref('https://example.com/audio.mp3');
</script>

2. 完整配置

vue 复制代码
<template>
  <Audio 
    :url="item.files.url"
    :speed="playbackSpeed"
    :noise-suppression="true"
    :disabled="isDisabled"
    :mock="isMockMode"
    :play-data="playData"
    :res="responseData"
    @play-click="handlePlayClick"
    @on-load="handleAudioLoad"
    @on-ended="handleAudioEnd"
  />
</template>

<script setup>
const playData = ref({
  id: 'audio-1',
  title: '音频标题'
});

const handlePlayClick = (data) => {
  console.log('播放状态变化:', data);
};

const handleAudioLoad = () => {
  console.log('音频加载完成');
};

const handleAudioEnd = () => {
  console.log('音频播放结束');
};
</script>

样式定制

组件提供了丰富的CSS类名供自定义样式:

scss 复制代码
.audio-zj {
  // 进度条样式定制
  .jindu {
    .el-slider__button {
      background: #f68842 !important;
      border: solid 2px #f68842;
      width: 11px;
      height: 11px;
    }
    .el-slider__bar {
      background-color: #f68842;
      height: 3px;
    }
  }
  
  // 禁用状态样式
  &.audio-disabled {
    cursor: not-allowed;
    .a-icon {
      opacity: 0.5;
    }
  }
}

高级特性

1. 多音频管理

通过Pinia store实现全局音频管理,确保用户体验:

typescript 复制代码
// 播放新音频时自动暂停其他音频
audioStore.pauseAllExcept(audioPlayer.value);

2. 播放速度控制

typescript 复制代码
watch(
  () => props.speed,
  newVal => {
    if (audioPlayer.value) {
      audioPlayer.value.playbackRate = newVal;
    }
  },
);

3. 降噪功能

typescript 复制代码
watch(
  () => props.noiseSuppression,
  newVal => {
    if (audioPlayer.value) {
      audioPlayer.value.noiseSuppression = newVal;
    }
  },
);

最佳实践

  1. 性能优化:使用定时器更新进度时注意清理,避免内存泄漏
  2. 用户体验:同时只允许一个音频播放,避免声音冲突
  3. 错误处理:对音频加载失败、网络异常等情况进行处理
  4. 响应式设计:确保组件在不同设备上的显示效果
  5. 无障碍访问:添加适当的ARIA标签和键盘导航支持

总结

本文介绍的Vue3音频组件具有以下优势:

  • 🎯 功能完整:涵盖播放控制、进度管理、时间显示等核心功能
  • 🔧 高度可配置:支持多种配置选项和自定义样式
  • 🚀 性能优秀:合理的状态管理和生命周期处理
  • 🎨 用户友好:直观的界面设计和流畅的交互体验
  • 🔒 稳定可靠:完善的错误处理和边界情况考虑

这个组件可以直接应用到各种需要音频播放功能的Vue3项目中,为用户提供优质的音频播放体验。


如果这篇文章对你有帮助,请点赞收藏!有任何问题欢迎在评论区讨论交流。如果需要音频组件整体源码的也可以私信博主。

相关推荐
CodeTransfer2 分钟前
css中animation与js的绑定原来还能这样玩。。。
前端·javascript
言之。1 小时前
Web技术构建桌面应用-Tauri框架和Electron框架
前端·javascript·electron
萌萌哒草头将军1 小时前
Node.js v24.7.0 新功能预览 🚀🚀🚀
前端·javascript·node.js
程序员张31 小时前
Vue3+ElementPlus—高效存储和回显多选项的状态值
javascript·vue.js·前端框架
艾小码2 小时前
90%前端忽略的3大内存黑洞,这样根治性能飙升300%!
前端·javascript·性能优化
GISer_Jing2 小时前
React Native核心技术深度解析_Trip Footprints
javascript·react native·react.js
Mintopia2 小时前
AIGC 多模态大模型在 Web 场景中的融合技术与挑战
前端·javascript·aigc
Mintopia2 小时前
🛡️ Next.js 中间件权限验证与 API 保护的奇幻冒险
前端·javascript·next.js
叶浩成5202 小时前
Clerk 用户认证系统集成文档
javascript·vue3·clerk
Miracle_G2 小时前
每日一个知识点:几分钟学会页面拖拽分隔布局的实现
前端·javascript