js通过浏览器调用电脑麦克风进行录音

129 阅读2分钟

代码基于vue3,chrome版本122.0.6261.131,未进行其他浏览器兼容性验证。拷贝到工程文件中进行简单调整即可使用。

<template>
  <div>
    <el-dialog :model-value="props.isAudioRecordDialogShow" width="900" :before-close="handleClose">
      <template #header="{ titleId, titleClass }">
        <div class="my-header">
          <h4 :id="titleId" :class="titleClass">音频录制</h4>
        </div>
      </template>
      <div class="main-content">
        <SvgIcon name="audio-wave" v-show="!isRecording"></SvgIcon>
        <img v-show="isRecording" src="/wave2.gif" alt="" style="width: 422px; height: 146px" />
        <div class="audio-ing" @click="startRecording" v-if="!recordedAudioURL">
          <div class="circle"></div>
          <div v-if="isRecording">
            <div class="pause"></div>
            <div class="text-btn">停止录音</div>
          </div>
          <div v-else>
            <div class="play triangle"></div>
            <div class="text-btn">开始录音</div>
          </div>
          <div class="header-timer">耗时:{{ timeStr }}</div>
        </div>
        <div v-if="!isRecording && recordedAudioURL" class="audio-res">
          <audio :src="recordedAudioURL" controls class="audio-self"></audio>
          <div class="re-record" @click="resetData">重新录制</div>
        </div>
      </div>
      <template #footer>
        <div class="dialog-footer">
          <el-button type="primary" :disabled="isRecording" @click="toUse" style="width: 140px">使用</el-button>
        </div>
      </template>
    </el-dialog>
  </div>
</template>

<script setup>
import { computed, ref, watch } from 'vue';
import { ElDialog, ElButton, ElMessage } from 'element-plus';
import SvgIcon from '@/components/SvgIcon';

const props = defineProps({
  isAudioRecordDialogShow: {
    type: Boolean,
    default: false,
  },
});

const btnText = computed(() => (isRecording.value ? '停止录音' : '开始录音'));
let recordingTime = ref(0);

const emit = defineEmits(['closeAudioRecordDialog', 'setRecordAudioUrl']);
const handleClose = () => {
  emit('closeAudioRecordDialog');
};

let timeStr = computed(() => {
  let h = Math.floor(recordingTime.value / 3600);
  let m = Math.floor((recordingTime.value % 3600) / 60);
  let s = recordingTime.value % 60;
  // 进行补0操作
  h = h < 10 ? `0${h}` : h;
  m = m < 10 ? `0${m}` : m;
  s = s < 10 ? `0${s}` : s;
  return `${h}:${m}:${s}`;
});

// 音频录制超过5秒后,自动停止录音
watch(
  () => recordingTime.value,
  (newV) => {
    if (newV >= 20) {
      stopRecording();
      ElMessage.warning('录音时长达到20秒,自动停止录音');
    }
  },
  {
    immediate: true,
  },
);

const isRecording = ref(false);
let mediaRecorder;
let chunks = [];
let recordedAudioURL = ref(null);
let recordingInterval;
const recorderFile = ref(null);

const startRecording = async () => {
  try {
    // 如果正在录音,点击按钮则停止录音
    if (mediaRecorder && isRecording.value) {
      stopRecording();
      return;
    }
    // 开始录音
    resetData();
    const stream = await navigator.mediaDevices.getUserMedia({ audio: true });
    mediaRecorder = new MediaRecorder(stream);

    mediaRecorder.ondataavailable = (event) => {
      if (event.data.size > 0) {
        chunks.push(event.data);
      }
    };

    mediaRecorder.onstop = () => {
      recorderFile.value = new Blob(chunks, { type: 'audio/wav' });
      recordedAudioURL.value = URL.createObjectURL(recorderFile.value);
      chunks = [];
      clearInterval(recordingInterval);
      recordingTime.value = 0;
    };

    mediaRecorder.start();
    isRecording.value = true;
    startRecordingTime();
  } catch (error) {
    // console.error('Error accessing media devices: ', error);
  }
};

const stopRecording = () => {
  if (mediaRecorder && isRecording.value) {
    mediaRecorder.stop();
    isRecording.value = false;
    clearInterval(recordingInterval);
    recordingTime.value = 0;
  }
};

const resetData = () => {
  recordedAudioURL.value = null;
  recordingTime.value = 0;
  timeStr.value = '00:00:00';
};

const startRecordingTime = () => {
  recordingInterval = setInterval(() => {
    recordingTime.value++;
  }, 1000);
};

const toUse = () => {
  if (!recordedAudioURL.value) {
    ElMessage.warning('请先录制音频');
    return;
  }
  // 拿到录制的音频的文件流数据
  let file = new File([recorderFile.value], 'audio.wav', { type: 'audio/wav' });
  emit('setRecordAudioUrl', file, recordedAudioURL.value);
  handleClose();
};
</script>

<style lang="less" scoped>
.main-content {
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
}
.svg-icon {
  width: 480px;
  height: 146px;
}
.audio-ing {
  width: 480px;
  height: 56px;
  background-color: var(--el-color-primary);
  border-radius: 300px;
  cursor: pointer;
}
.circle {
  width: 43px;
  height: 43px;
  background-color: #fff;
  border-radius: 50%;
  float: left;
  margin-left: 7px;
  margin-top: 7px;
}
.triangle {
  width: 0;
  height: 0;
  border-top: 10px solid transparent;
  border-bottom: 10px solid transparent;
  border-left: 15px solid var(--el-color-primary);
  float: left;
  margin-top: 20px;
  margin-left: -27px;
}
.pause {
  width: 16px;
  height: 20px;
  position: relative;
  float: left;
  margin-top: 22px;
  margin-left: -30px;
}
.pause::before {
  content: '';
  position: absolute;
  width: 5px;
  height: 14px;
  background-color: var(--el-color-primary);
  left: 0;
}
.pause::after {
  content: '';
  position: absolute;
  width: 5px;
  height: 14px;
  background-color: var(--el-color-primary);
  right: 0;
}
.header-timer {
  float: right;
  margin-top: 17px;
  margin-right: 18px;
  color: #fff;
}
.text-btn {
  float: left;
  margin-left: 10px;
  margin-top: 17px;
  color: #fff;
}
.dialog-footer {
  text-align: center;
  margin-top: 40px;
}
.audio-res {
  position: relative;
  height: 56px;
  display: flex;
  align-items: center;
  .audio-self {
    width: 467px;
  }
  .re-record {
    position: absolute;
    right: -90px;
    top: 15px;
    cursor: pointer;
    color: var(--el-color-primary);
    font-size: 16px;
  }
}
</style>