npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

send-record

v0.0.2

Published

--- title: 怎么录制语音并播放 tags: vue categories: vue theme: vue-pro highlight: ---

Downloads

2

Readme


title: 怎么录制语音并播放 tags: vue categories: vue theme: vue-pro highlight:

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

需要的数据

  • 录制的三种状态:录制前、录制中、录制后。
  • 录制器实例
  • 录制时间显示
  • 录制器当前状态
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
  }`;
}

开始录制

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

结束录制

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

播放录制的音频

function playRecord() {
  recorder.play();
}

停止录制的音频

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

重置到录制前

function resetRecord() {
  curRecordStatus.value = WILL_RECORD;
}

获取wav文件和下载

function downloadWAV(){
   console.log('wav: ', recorder.getWAVBlob());
            recorder.downloadWAV();
}

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

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

与之相对应的的方法:

  • start: 开始录制
  • stop: 结束录制
  • play: 播放
  • pausePlay: 暂停播放
  • resumePlay: 恢复播放
  • stopPlay: 停止播放
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');
};

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

哦,还有一层 recorder2

上传音频文件并播放

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

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体验

recorder3.gif

<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>