怎么录制语音并播放

录制语音主要借助js-audio-recorder库,官方案例展示官方案例代码

这边也尝试用下api,写个简短的DEMO。

DEMO体验

shell 复制代码
npm i send-record
# <SendRecord/>

需要的数据

  • 录制的三种状态:录制前、录制中、录制后。
  • 录制器实例
  • 录制时间显示:这里根据录制器的时间自动计算
  • 录制器当前状态
js 复制代码
import { reactive, computed, ref } from 'vue';

// @ts-ignore
import Recorder from 'js-audio-recorder';
const WILL_RECORD = 1; // 录制前
const RECORDING = 2; // 正在录制,点击可以停止
const RECORDED = 3; // 录制结束,可以其他操作

// 录制器
const recorder = reactive < any > new Recorder();
// 录制时间显示,这里根据录制器的时间自动计算
const durationShow = computed(() => durationToStr(recorder.duration));
// 当前是哪种状态
const curRecordStatus = ref(WILL_RECORD);

/**
 * 将秒数转换为时分秒: 2.1 => 00:02
 * @param duration 秒数 2
 * @returns {string} 00:02
 */
function durationToStr(duration: number) {
  const sumSeconds = Math.floor(duration);
  const minute = Math.floor(sumSeconds / 60);
  const second = sumSeconds % 60;
  return `${minute < 10 ? '0' + minute : minute}:${
    second < 10 ? '0' + second : second
  }`;
}

开始录制

  • 开始录音
  • 设置状态为 录制中
js 复制代码
function startRecord() {
  recorder.start().then(
    () => {
      console.log('开始录音');
    },
    (error) => {
      console.log(`异常了,${error.name}:${error.message}`);
    }
  );
  curRecordStatus.value = RECORDING;
}

结束录制

  • 结束录制
  • 设置状态为 已录制
js 复制代码
function endRecord() {
  recorder?.stop();
  curRecordStatus.value = RECORDED;
}

播放录制的音频

js 复制代码
function playRecord() {
  recorder.play();
}

停止录制的音频

js 复制代码
function pauseRecord() {
  recorder.pausePlay();
}

重置到录制前

js 复制代码
function resetRecord() {
  curRecordStatus.value = WILL_RECORD;
}

获取wav文件和下载

js 复制代码
function downloadWAV(){
   console.log('wav: ', recorder.getWAVBlob());
   recorder.downloadWAV();
}

录制器的监听事件和常用方法

  • onprogress: 监听录制中
  • onplay: 监听播放
  • onpauseplay: 监听暂停播放
  • onresumeplay: 监听恢复播放
  • onstopplay: 监听停止播放
  • onplayend: 监听播放结束

与之相对应的的方法:

  • start: 开始录制
  • stop: 结束录制
  • play: 播放
  • pausePlay: 暂停播放
  • resumePlay: 恢复播放
  • stopPlay: 停止播放
js 复制代码
recorder.onprogress = (params) => {
  console.log('音频总数据:', params);
};
recorder.onplay = () => {
  console.log('%c回调监听,开始播放音频', 'color: #2196f3');
};
recorder.onpauseplay = () => {
  console.log('%c回调监听,暂停播放音频', 'color: #2196f3');
};
recorder.onresumeplay = () => {
  console.log('%c回调监听,恢复播放音频', 'color: #2196f3');
};
recorder.onstopplay = () => {
  console.log('%c回调监听,停止播放音频', 'color: #2196f3');
};
recorder.onplayend = () => {
  recorder.isplaying = false;
  console.log('%c回调监听,音频已经完成播放', 'color: #2196f3');
};

其他属性和方法如下,其文档地址好像丢失:

哦,还有一层

上传音频文件并播放

音频文件播放,可以借助audio元素,播放前必须要先加载。

js 复制代码
const audioEl = ref(new Audio());
  
 async function playUploadedAudio (e)  {
    const audioFile = e.target.files[0]
    if(audioFile.isLoaded) {
        audioEl.value.play();
        return;
    }
    await readyAudio(audioFile);
    audioEl.value.play();
 }
  
function readyAudio(audioFile: any) {
  return new Promise((resolve, reject) => {
    var url = URL.createObjectURL(audioFile);
    var audioElement = new Audio(url);
    audioEl.value = audioElement

    audioElement.addEventListener('loadedmetadata', () => {
      audioFile.isLoaded = true;
      resolve(audioElement);
    });
    // 出错就reject
    audioElement.addEventListener('error', reject);
  });
}

DEMO代码

vue 复制代码
<script setup name="SendRecord" lang="ts">
import { reactive, computed, ref } from 'vue';
// @ts-ignore
import Recorder from 'js-audio-recorder';
const WILL_RECORD = 1; // 录制前
const RECORDING = 2; // 正在录制,点击可以停止
const RECORDED = 3; // 录制结束,可以其他操作

const recorder = reactive<any>(new Recorder());
const durationShow = computed(() => durationToStr(recorder.duration));
const curRecordStatus = ref(WILL_RECORD);

function startOrendRecord() {
  if (curRecordStatus.value === WILL_RECORD) {
    startRecord();
    return;
  }
  if (curRecordStatus.value === RECORDING) {
    endRecord();
    return;
  }
}
function startRecord() {
  console.log(recorder);
  recorder.start().then(
    () => {
      console.log('开始录音');
    },
    (error) => {
      console.log(`异常了,${error.name}:${error.message}`);
    }
  );
  curRecordStatus.value = RECORDING;
}

function endRecord() {
  recorder?.stop();
  console.log(recorder.file);
  curRecordStatus.value = RECORDED;
}
function playRecord() {
  recorder.play();
}

function pauseRecord() {
  recorder.pausePlay();
}

function resetRecord() {
  curRecordStatus.value = WILL_RECORD;
}
function downloadWAV() {
  console.log('wav: ', recorder.getWAVBlob());
  recorder.downloadWAV();
}
/**
 * 将秒数转换为时分秒: 2.1 => 00:02
 * @param duration 秒数 2
 * @returns {string} 00:02
 */
function durationToStr(duration: number) {
  const sumSeconds = Math.floor(duration);
  const minute = Math.floor(sumSeconds / 60);
  const second = sumSeconds % 60;
  return `${minute < 10 ? '0' + minute : minute}:${
    second < 10 ? '0' + second : second
  }`;
}
recorder.onprogress = (params) => {
  console.log('音频总数据:', params);
};
recorder.onplay = () => {
  console.log('%c回调监听,开始播放音频', 'color: #2196f3');
};
recorder.onpauseplay = () => {
  console.log('%c回调监听,暂停播放音频', 'color: #2196f3');
};
recorder.onresumeplay = () => {
  console.log('%c回调监听,恢复播放音频', 'color: #2196f3');
};
recorder.onstopplay = () => {
  console.log('%c回调监听,停止播放音频', 'color: #2196f3');
};
recorder.onplayend = () => {
  recorder.isplaying = false;
  console.log('%c回调监听,音频已经完成播放', 'color: #2196f3');
};

const audioEl = ref(new Audio());

async function playUploadedAudio(e) {
  const audioFile = e.target.files[0];
  if (audioFile.isLoaded) {
    audioEl.value.play();
    return;
  }
  await readyAudio(audioFile);
  audioEl.value.play();
}

function readyAudio(audioFile: any) {
  return new Promise((resolve, reject) => {
    var url = URL.createObjectURL(audioFile);
    var audioElement = new Audio(url);
    audioEl.value = audioElement;

    audioElement.addEventListener('loadedmetadata', () => {
      audioFile.isLoaded = true;
      resolve(audioElement);
    });
    // 出错就reject
    audioElement.addEventListener('error', reject);
  });
}
</script>

<template>
  <div>
    <div ref="recorderIconBox" class="icon-box" @click="startOrendRecord">
      <svg width="20" height="20" t="1693473966123" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4112" > <path d="M827.246871 451.075419c-12.94994-0.588401-23.925922 9.432837-24.51637 22.382776-0.093121 2.062985-0.418532 6.353708-1.106194 12.542664-1.170662 10.54824-2.959402 22.35924-5.490038 35.106566-7.226588 36.413328-18.898419 72.794933-35.917024 106.534362-47.672766 94.508467-126.925784 150.334937-248.217245 150.71663-121.290437-0.381693-200.546525-56.208163-248.217245-150.71663-17.018605-33.739429-28.692482-70.120011-35.919071-106.534362-2.529613-12.747325-4.317329-24.558325-5.487991-35.106566-0.687662-6.188956-1.014096-10.479679-1.108241-12.542664-0.588401-12.94994-11.564383-22.971178-24.514323-22.382776-12.951987 0.588401-22.973224 11.564383-22.382776 24.51637 0.5137 11.339256 2.63092 30.394241 7.446599 54.654784 8.000208 40.316218 20.946055 80.665181 40.051181 118.537743 51.840692 102.776781 138.972145 167.127392 265.456884 175.017082l0 85.599563L291.185872 909.400962c-12.96529 0-23.473621 10.510378-23.473621 23.473621 0 12.96529 10.508331 23.473621 23.473621 23.473621l441.857477 0c12.963243 0 23.473621-10.508331 23.473621-23.473621 0-12.963243-10.510378-23.473621-23.473621-23.473621L534.272259 909.400962l0-85.454254c127.791501-7.209192 215.690434-71.734788 267.86063-175.162392 19.104103-37.872562 32.050973-78.221526 40.051181-118.537743 4.815679-24.260543 6.930853-43.315528 7.446599-54.654784C850.217025 462.639802 840.197834 451.66382 827.246871 451.075419zM510.171352 700.19215c106.568131 0 193.353706-86.506213 193.353706-193.220676L703.525058 260.871449c0-106.59269-86.567611-193.220676-193.353706-193.220676-106.570177 0-193.353706 86.508259-193.353706 193.220676l0 246.100024C316.817646 613.567233 403.385257 700.19215 510.171352 700.19215zM363.764887 260.871449c0-80.693834 65.674769-146.273435 146.407488-146.273435 80.8197 0 146.407488 65.570391 146.407488 146.273435l0 246.100024c0 80.69588-65.674769 146.273435-146.407488 146.273435-80.8197 0-146.407488-65.568345-146.407488-146.273435L363.764887 260.871449z" fill="#272636" p-id="4113" ></path> </svg>
    </div>
    <div>
      <span v-if="curRecordStatus === RECORDING || curRecordStatus === RECORDED" >{{ durationShow }}</span >
    </div>
    <div v-show="curRecordStatus === RECORDED" class="icon-box">
      <!-- 播放图标 -->
      <svg  @click="playRecord" v-if="!recorder.isplaying" width="20" height="20" t="1693477278174" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="5083" > <path d="M512 0C230.4 0 0 230.4 0 512s230.4 512 512 512 512-230.4 512-512S793.6 0 512 0z m0 981.333333C253.866667 981.333333 42.666667 770.133333 42.666667 512S253.866667 42.666667 512 42.666667s469.333333 211.2 469.333333 469.333333-211.2 469.333333-469.333333 469.333333z" fill="#515151" p-id="5084" ></path> <path d="M672 441.6l-170.666667-113.066667c-57.6-38.4-106.666667-12.8-106.666666 57.6v256c0 70.4 46.933333 96 106.666666 57.6l170.666667-113.066666c57.6-42.666667 57.6-106.666667 0-145.066667z" fill="#515151" p-id="5085" ></path> </svg>
      <!-- 暂停图标 -->
      <svg v-else @click="pauseRecord" width="20" height="20" t="1693477687958" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6103" > <path d="M511.474 884.11c-205.692 0-373.042-167.083-373.042-372.46 0-205.371 167.35-372.46 373.042-372.46 205.698 0 373.042 167.089 373.042 372.46 0 205.376-167.344 372.46-373.042 372.46m0-819.412c-246.84 0-447.649 200.496-447.649 446.951 0 246.457 200.809 446.951 447.649 446.951 246.842 0 447.649-200.494 447.649-446.951 0-246.455-200.808-446.951-447.649-446.951m127.768 580.701V378.6c0-20.573-16.701-37.247-37.301-37.247-20.606 0-37.307 16.675-37.307 37.247v266.799c0 20.574 16.701 37.249 37.307 37.249 20.601 0 37.301-16.675 37.301-37.249m-179.876 0V378.6c0-20.573-16.701-37.247-37.301-37.247-20.606 0-37.307 16.675-37.307 37.247v266.799c0 20.574 16.701 37.249 37.307 37.249 20.6 0 37.301-16.675 37.301-37.249" fill="#515151" p-id="6104" ></path> </svg>
      <button @click="downloadWAV">下载wav</button><br />
    </div>
    <div v-show="curRecordStatus === RECORDED" class="icon-box" @click="resetRecord" >
      <svg t="1693479917670" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7988" width="20" height="20" > <path d="M510.825 162c-98.288 0-187.097 40.292-250.839 105.148V182h-60.201v200h200.672v-60H291.104c53.349-61.229 131.967-100 219.721-100 160.7 0 290.974 129.837 290.974 290 0 160.162-130.274 290-290.974 290-147.075 0-268.592-108.777-288.152-250H162c19.926 174.451 168.456 310 348.825 310C704.773 862 862 705.3 862 512S704.773 162 510.825 162z" fill="#515151" p-id="7989" ></path> </svg>
    </div>
    <div style="margin-top: 300px">
      <input type="file" @change="playUploadedAudio" />
    </div>
  </div>
</template>

<style scoped>
.icon-box {
  padding: 10px;
}
</style>
相关推荐
光影少年几秒前
vue2与vue3的全局通信插件,如何实现自定义的插件
前端·javascript·vue.js
As977_2 分钟前
前端学习Day12 CSS盒子的定位(相对定位篇“附练习”)
前端·css·学习
susu10830189114 分钟前
vue3 css的样式如果background没有,如何覆盖有background的样式
前端·css
Ocean☾5 分钟前
前端基础-html-注册界面
前端·算法·html
Rattenking6 分钟前
React 源码学习01 ---- React.Children.map 的实现与应用
javascript·学习·react.js
Dragon Wu8 分钟前
前端 Canvas 绘画 总结
前端
CodeToGym12 分钟前
Webpack性能优化指南:从构建到部署的全方位策略
前端·webpack·性能优化
~甲壳虫13 分钟前
说说webpack中常见的Loader?解决了什么问题?
前端·webpack·node.js
~甲壳虫17 分钟前
说说webpack proxy工作原理?为什么能解决跨域
前端·webpack·node.js
Cwhat19 分钟前
前端性能优化2
前端