录音
使用 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',
},
},
},
})