Skip to content

录音

使用 WebRTC 访问用户麦克风, 主要用到两个包

在不同的环境中, 可能需要不同的包, 所以API不同, 所以每次都要重新写, 所以不如直接封装一个通用的API, 到时候直接修改import即可

vue
<template>
  <div>
    <button @click="handleStart">开始录音</button>
    <button @click="handleStop">结束录音(获得文件)</button>
  </div>
</template>

<script setup>
import { useRecorder } from "@/hooks";
const recorder = useRecorder();

function createAudioWithBlob(blob) {
  const audio = document.createElement("audio");
  const url = URL.createObjectURL(blob);
  audio.src = url;
  audio.controls = true;
  document.body.append(audio);
}

function handleStart() {
  recorder.init({
    onResolve: () => recorder.start(),
  });
}

async function handleStop() {
  recorder.stop();
  const blob = await recorder.getBlob()
  console.log("blob", blob);
  createAudioWithBlob(blob);
}
</script>
js
import Recorder from "js-audio-recorder";

const isCallable = (fn) => typeof fn === "function";

export function useRecorder() {
  let recorderInst = null; // 初始化
  let isInitialize = false; // 是否

  // 初始化
  function init(opts = {}) {
    if (isInitialize) {
      return;
    }
    const defaultOptions = {
      sampleBits: 16, // 采样位数, 支持 8 或 16, 默认是16
      sampleRate: 16000, // 采样率,支持 11025、16000、22050、24000、44100、48000, 根据浏览器默认值, chrome是48000
      numChannels: 1, // 声道支持 1 或 2, 默认是1
    };
    const options = Object.assign(defaultOptions, opts);
    recorderInst = new Recorder(options);
    isInitialize = true;

    // call onResolve
    const { onResolve } = options;
    isCallable(onResolve) && onResolve(recorderInst);
  }

  // 未初始化就抛出异常
  function _throwErrorWhenNotInit() {
    if (!isInitialize) {
      throw new Error("[start]please init recorder first");
    }
  }

  // 开始录音
  function start() {
    _throwErrorWhenNotInit();
    recorderInst.start().then(() => {
      console.log("[start]success to start recorder");
    }).catch(() => {
      isCallable(onReject) && onReject(recorderInst);
      console.error("[start]failed to start recorder");
    });
  }

  // 结束录音: 并获取 blob 对象
  function stop() {
    _throwErrorWhenNotInit();
    recorderInst.stop();
  }

  // 获取录音结果
  function getBlob() {
    _throwErrorWhenNotInit();
    const blob = recorderInst.getWAVBlob();
    return Promise.resolve(blob);
  }

  // 获取录音机实例
  function getRecorderInst() {
    _throwErrorWhenNotInit();
    return recorderInst;
  }

  return {
    init,
    getRecorderInst,
    start,
    stop,
    getBlob,
  }
}
js
import Recorder from "recorderjs-ex";

const isCallable = (fn) => typeof fn === "function";

export function useRecorder() {
  let recorderInst = null; // 初始化
  let isInitialize = false; // 是否

  // 初始化
  function init(opts = {}) {
    if (isInitialize) {
      return;
    }
    isInitialize = true;
    const defaultOptions = {
      numChannels: 2,
    };

    const { onResolve, onReject } = opts;
    return navigator.mediaDevices.getUserMedia({ audio: true }).then((stream) => {
      const audioContext = new AudioContext({});
      const source = audioContext.createMediaStreamSource(stream);
      const options = Object.assign(defaultOptions, opts);
      recorderInst = new Recorder(source, options);
      isCallable(onResolve) && onResolve(recorderInst);
    }).catch((err) => {
      console.log("[getUserMedia]failed to get userMedia");
      isCallable(onReject) && onReject(err);
    });
  }

  // 未初始化就抛出异常
  function _throwErrorWhenNotInit() {
    if (!isInitialize) {
      console.error("[start]please init recorder first");
    }
  }

  // 开始录音
  function start() {
    _throwErrorWhenNotInit();
    recorderInst.record();
  }

  // 结束录音: 并获取 blob 对象
  function stop() {
    _throwErrorWhenNotInit();
    recorderInst.stop();
  }

  // 获取录音结果
  function getBlob(rate = 8000) {
    _throwErrorWhenNotInit();
    return new Promise((resolve) => {
      recorderInst.exportWAV(resolve, "audio/wav", rate);
    });
  }

  // 获取录音机实例
  function getRecorderInst() {
    _throwErrorWhenNotInit();
    return recorderInst;
  }

  return {
    init,
    getRecorderInst,
    start,
    stop,
    getBlob,
  }
}
sh
pnpm i js-audio-recorder
pnpm i recorderjs-ex

使用vite打包的注意

如果是使用 recorderjs-ex, 在 vite 中打包需要设置打包选项

js
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'

// https://vite.dev/config/
export default defineConfig({
  plugins: [vue()],
  // other settings
  optimizeDeps: {
    esbuildOptions: {
      define: {
        global: 'globalThis',
      },
    },
  },
})

Released under the MIT License.