Vue3 实现麦克风实时音量检测

1 前言

本文基于 Vue3 + TypeScript 实现实时音量检测,其他语言可修改相应写法
在这里插入图片描述

2 功能实现

2.1 新建 vumeter.js

在 public/static 目录下新建 vumeter.js

const SMOOTHING_FACTOR = 0.8;
registerProcessor(
  'vumeter',
  class extends AudioWorkletProcessor {
    _volume;
    _updateIntervalInMS;
    _nextUpdateFrame;
    _currentTime;

    constructor() {
      super();
      this._volume = 0;
      this._updateIntervalInMS = 25;
      this._nextUpdateFrame = this._updateIntervalInMS;
      this._currentTime = 0;
      this.port.onmessage = (event) => {
        if (event.data.updateIntervalInMS) {
          this._updateIntervalInMS = event.data.updateIntervalInMS;
        }
      };
    }

    get intervalInFrames() {
      return (this._updateIntervalInMS / 1000) * sampleRate;
    }

    process(inputs) {
      const input = inputs[0];
      if (input.length > 0) {
        const samples = input[0];
        let sum = 0;
        let rms = 0;
        for (let i = 0; i < samples.length; i += 1) {
          sum += samples[i] * samples[i];
        }
        rms = Math.sqrt(sum / samples.length);
        this._volume = Math.max(rms, this._volume * SMOOTHING_FACTOR);
        this._nextUpdateFrame -= samples.length;
        if (0 > this._nextUpdateFrame) {
          this._nextUpdateFrame += this.intervalInFrames;
          if (!this._currentTime || 0.125 < currentTime - this._currentTime) {
            this._currentTime = currentTime;
            this.port.postMessage({ volume: this._volume });
          }
        }
      }

      return true;
    }
  },
);

2.2 新建 useVolume.ts

在 src/hooks 目录下新建 vumeter.js

const volume = ref(0)
let audioContext: AudioContext
let node: AudioWorkletNode
let stream: MediaStream | null = null
let source: MediaStreamAudioSourceNode | null = null

const getStaticPath = (path: string) => `${import.meta.env.VITE_PUBLIC_PATH}static/${path}`

const init = async () => {
  audioContext = new AudioContext()
  await audioContext.audioWorklet.addModule(getStaticPath('vumeter.js'))
  node = new AudioWorkletNode(audioContext, 'vumeter')
  node.port.onmessage = (event) => {
    if (event.data.volume) {
      volume.value = Math.round(event.data.volume * 200)
    }
  }
}

const disconnectAudioContext = () => {
  stream?.getTracks().forEach((track) => track.stop()) // 取消麦克风占用
  source?.disconnect() // 取消 onmessage 监听
  stream = null
  source = null
  volume.value = 0
}

const connectAudioContext = async () => {
  if (!audioContext) await init()
  navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(async (mediaStream) => {
    stream = mediaStream
    source = audioContext.createMediaStreamSource(stream)
    source.connect(node)
  })
}

export default function () {
  return {
    volume,
    connectAudioContext,
    disconnectAudioContext
  }
}

2.3 新增音量检测展示容器

<template>
  <div class="volume">
    <div>当前麦克风</div>
    <div>输入音量大小如下</div>
    <el-icon><i-ep-Microphone /></el-icon>
    <span
      v-for="i in 20"
      :key="i"
      class="volume-span"
      :style="`${Math.ceil(volume / 5) >= i ? 'background: #2559e5' : ''}`"
    ></span>
  </div>
</template>

<script setup lang="ts">
import useVolume from '@/hooks/useVolume'
const { volume, connectAudioContext, disconnectAudioContext } = useVolume()

// 开始音量检测
connectAudioContext()

// 暂停音量检测
// disconnectAudioContext()
</script>

<style lang="scss" scoped>
.volume {
  border-top: 1px solid #dcdfe6;
  border-bottom: 1px solid #dcdfe6;
  padding: 16px 0;
  text-align: center;
  font-size: 12px;
  :nth-child(2) {
    margin-bottom: 6px;
  }
  div {
    color: #909399;
  }
  .volume-span {
    display: inline-block;
    margin-left: 1px;
    width: 3px;
    height: 8px;
    background: #c6e2ff;
  }
}
</style>
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值