uniapp中使用网页录音并上传声音文件(发语音)——js-audio-recorder的使用【伸手党福利】

2023-11-11

uniapp中上传音频只能在app或小程序当中实现,如何使用网页完成语音的录制和上传则成为了困扰前端童鞋的重点。
本文着重解决:
js-audio-recorder报 error:浏览器不支持getUserMedia ! 的问题。
js-audio-recorder报 NotFoundError : Requested device not found 的问题
js-audio-recorder的使用、demo以及文件上传的问题。

本篇文章主要讲如何使用uniapp(vue)在网页中(web模式下)录制声音并上传
js-audio-recorder插件支持在微信公众号(网页)、UC浏览器、Chrome及其内核的浏览器内运行。

文章前半部分先讲操作和demo,后半部分再讲原理和插件来源。

demo编写——前期准备:

  1. 安装插件
yarn add js-audio-recorder

如果需要转mp3加一句:

yarn add lamejs
  1. 建立vue文件

由于代码是从网上上复制下来的,个人做了一些修改,所以会看到一些注释。
此代码上传按钮会直接上传文件到后端,请修改为自己的后端地址


<template>
  <div class="home" style="margin:1vw;">
    <Button type="success" @click="getPermission()" style="margin:1vw;">获取麦克风权限</Button>
    <br/>
    <Button type="info" @click="startRecorder()"  style="margin:1vw;">开始录音</Button>
    <Button type="info" @click="resumeRecorder()" style="margin:1vw;">继续录音</Button>
    <Button type="info" @click="pauseRecorder()" style="margin:1vw;">暂停录音</Button>
    <Button type="info" @click="stopRecorder()" style="margin:1vw;">结束录音</Button>
    <br/>
    <Button type="success" @click="playRecorder()" style="margin:1vw;">录音播放</Button>
    <Button type="success" @click="pausePlayRecorder()" style="margin:1vw;">暂停录音播放</Button>
    <Button type="success" @click="resumePlayRecorder()" style="margin:1vw;">恢复录音播放</Button>
    <Button type="success" @click="stopPlayRecorder()" style="margin:1vw;">停止录音播放</Button>
    <br/>
    <Button type="info" @click="getRecorder()" style="margin:1vw;">获取录音信息</Button>
    <Button type="info" @click="downPCM()" style="margin:1vw;">下载PCM</Button>
    <Button type="info" @click="downWAV()" style="margin:1vw;">下载WAV</Button>
    <Button type="info" @click="getMp3Data()" style="margin:1vw;">下载MP3</Button>
    <br/>
    <Button type="error" @click="destroyRecorder()" style="margin:1vw;">销毁录音</Button>
    <br/>

	<Button type="error" @click="uploadWav()" style="margin:1vw;">上传录音</Button>
	<br/>
    <div style="width:100%;height:200px;border:1px solid red;">
      <canvas id="canvas"></canvas>
      <span style="padding: 0 10%;"></span>
      <canvas id="playChart"></canvas>
    </div>
  </div>
</template>
 
<script>
  import Recorder from 'js-audio-recorder'
  const lamejs = require('lamejs')
  const recorder = new Recorder({
    sampleBits: 16,                 // 采样位数,支持 8 或 16,默认是16
    sampleRate: 48000,              // 采样率,支持 11025、16000、22050、24000、44100、48000,根据浏览器默认值,我的chrome是48000
    numChannels: 1,                 // 声道,支持 1 或 2, 默认是1
    // compiling: false,(0.x版本中生效,1.x增加中)  // 是否边录边转换,默认是false
  })
 
  // 绑定事件-打印的是当前录音数据
  recorder.onprogress = function(params) {
    // console.log('--------------START---------------')
    // console.log('录音时长(秒)', params.duration);
    // console.log('录音大小(字节)', params.fileSize);
    // console.log('录音音量百分比(%)', params.vol);
    // console.log('当前录音的总数据([DataView, DataView...])', params.data);
 
    // console.log('--------------END---------------')
  }
  export default {
    name: 'home',
    data () {
      return {
        //波浪图-录音
        drawRecordId:null,
        oCanvas : null,
        ctx : null,
        //波浪图-播放
        drawPlayId:null,
        pCanvas : null,
        pCtx : null,
      }
    },
    mounted(){
      this.startCanvas();
    },
    methods: {
      /**
       * 波浪图配置
       * */
      startCanvas(){
        //录音波浪
        // this.oCanvas = document.getElementById('canvas');
        // this.ctx = this.oCanvas.getContext("2d");
        // //播放波浪
        // this.pCanvas = document.getElementById('playChart');
        // this.pCtx = this.pCanvas.getContext("2d");
      },
 
      /**
       *  录音的具体操作功能
       * */
      // 开始录音
      startRecorder () {
        recorder.start().then(() => {
          // this.drawRecord();//开始绘制图片
		  uni.showToast({
		  	
		  	title: '开始录音',
		  	
		  })
        }, (error) => {
          // 出错了
		  
		  
		  uni.showToast({
		  	
		  	title: `${error.name} : ${error.message}`,
		  	
		  })
          console.log(`${error.name} : ${error.message}`);
        });
      },
      // 继续录音
      resumeRecorder () {
        recorder.resume()
      },
      // 暂停录音
      pauseRecorder () {
        recorder.pause();
        this.drawRecordId && cancelAnimationFrame(this.drawRecordId);
        this.drawRecordId = null;
      },
      // 结束录音
      stopRecorder () {
        recorder.stop()
		uni.showToast({
			
			title: '结束录音',
			
		})
        this.drawRecordId && cancelAnimationFrame(this.drawRecordId);
        this.drawRecordId = null;
      },
      // 录音播放
      playRecorder () {
        recorder.play();
		uni.showToast({
			
			title: '录音播放',
			
		})
        // this.drawPlay();//绘制波浪图
      },
      // 暂停录音播放
      pausePlayRecorder () {
        recorder.pausePlay()
      },
      // 恢复录音播放
      resumePlayRecorder () {
        recorder.resumePlay();
        this.drawPlay();//绘制波浪图
      },
      // 停止录音播放
      stopPlayRecorder () {
        recorder.stopPlay();
      },
      // 销毁录音
      destroyRecorder () {
        recorder.destroy().then(function() {
          recorder = null;
          this.drawRecordId && cancelAnimationFrame(this.drawRecordId);
          this.drawRecordId = null;
        });
      },
      /**
       *  获取录音文件
       * */
      getRecorder(){
        let toltime = recorder.duration;//录音总时长
        let fileSize = recorder.fileSize;//录音总大小
 
        //录音结束,获取取录音数据
        let PCMBlob = recorder.getPCMBlob();//获取 PCM 数据
        let wav = recorder.getWAVBlob();//获取 WAV 数据
 
        let channel = recorder.getChannelData();//获取左声道和右声道音频数据
 
		console.log(toltime);
		console.log(fileSize);
		// console.log(PCMBlob);
		console.log(wav);
		console.log(channel);
		console.log(recorder);
		
      },
      /**
       *  下载录音文件
       * */
      //下载pcm
      downPCM(){
        //这里传参进去的时文件名
        recorder.downloadPCM('新文件');
      },
      //下载wav
      downWAV(){
        //这里传参进去的时文件名
        recorder.downloadWAV('新文件');
      },
      /**
       *  获取麦克风权限
       * */
      getPermission(){
        Recorder.getPermission().then(() => {
          this.$Message.success('获取权限成功')
		  uni.showToast({
		  	title: '没有找到您要查询的内容!'
		  })
        }, (error) => {
			uni.showToast({
				
				title: `${error.name} : ${error.message}`,
				
			})
          console.log(`${error.name} : ${error.message}`);
        });
      },
      /**
       * 文件格式转换 wav-map3
       * */
      getMp3Data(){
        const mp3Blob = this.convertToMp3(recorder.getWAV());
        recorder.download(mp3Blob, 'recorder', 'mp3');
      },
      blobToFile(theBlob, fileName) {  
        //将blob转换为file  
        let file = new File([theBlob], fileName, {type: theBlob.type.split('/')[1], lastModified: Date.now()});  
        return file;  
      },
      convertToMp3(wavDataView) {
        // 获取wav头信息
        const wav = lamejs.WavHeader.readHeader(wavDataView); // 此处其实可以不用去读wav头信息,毕竟有对应的config配置
        const { channels, sampleRate } = wav;
        const mp3enc = new lamejs.Mp3Encoder(channels, sampleRate, 128);
        // 获取左右通道数据
        const result = recorder.getChannelData()
        const buffer = [];
 
        const leftData = result.left && new Int16Array(result.left.buffer, 0, result.left.byteLength / 2);
        const rightData = result.right && new Int16Array(result.right.buffer, 0, result.right.byteLength / 2);
        const remaining = leftData.length + (rightData ? rightData.length : 0);
 
        const maxSamples = 1152;
        for (let i = 0; i < remaining; i += maxSamples) {
          const left = leftData.subarray(i, i + maxSamples);
          let right = null;
          let mp3buf = null;
 
          if (channels === 2) {
            right = rightData.subarray(i, i + maxSamples);
            mp3buf = mp3enc.encodeBuffer(left, right);
          } else {
            mp3buf = mp3enc.encodeBuffer(left);
          }
 
          if (mp3buf.length > 0) {
            buffer.push(mp3buf);
          }
        }
 
        const enc = mp3enc.flush();
 
        if (enc.length > 0) {
          buffer.push(enc);
        }
		
		
        return new Blob(buffer, { type: 'audio/mp3' });
      },
 
      /**
       * 绘制波浪图-录音
       * */
      drawRecord () {
 //        // 用requestAnimationFrame稳定60fps绘制
 //        this.drawRecordId = requestAnimationFrame(this.drawRecord);
 
 //        // 实时获取音频大小数据
 //        let dataArray = recorder.getRecordAnalyseData(),
 //            bufferLength = dataArray.length;
 
 //        // 填充背景色
 //        this.ctx.fillStyle = 'rgb(200, 200, 200)';
 //        this.ctx.fillRect(0, 0, this.oCanvas.width, this.oCanvas.height);
 
 //        // 设定波形绘制颜色
 //        this.ctx.lineWidth = 2;
 //        this.ctx.strokeStyle = 'rgb(0, 0, 0)';
 
 //        this.ctx.beginPath();
 
 //        var sliceWidth = this.oCanvas.width * 1.0 / bufferLength, // 一个点占多少位置,共有bufferLength个点要绘制
 //                x = 0;          // 绘制点的x轴位置
 
 //        for (var i = 0; i < bufferLength; i++) {
 //          var v = dataArray[i] / 128.0;
 //          var y = v * this.oCanvas.height / 2;
 
 //          if (i === 0) {
 //            // 第一个点
 //            this.ctx.moveTo(x, y);
 //          } else {
 //            // 剩余的点
 //            this.ctx.lineTo(x, y);
 //          }
 //          // 依次平移,绘制所有点
 //          x += sliceWidth;
 //        }
 
 //        this.ctx.lineTo(this.oCanvas.width, this.oCanvas.height / 2);
 //        this.ctx.stroke();
      },
      /**
       * 绘制波浪图-播放
       * */
      drawPlay () {
 //        // 用requestAnimationFrame稳定60fps绘制
 //        this.drawPlayId = requestAnimationFrame(this.drawPlay);
 
 //        // 实时获取音频大小数据
 //        let dataArray = recorder.getPlayAnalyseData(),
 //                bufferLength = dataArray.length;
 
 //        // 填充背景色
 //        this.pCtx.fillStyle = 'rgb(200, 200, 200)';
 //        this.pCtx.fillRect(0, 0, this.pCanvas.width, this.pCanvas.height);
 
 //        // 设定波形绘制颜色
 //        this.pCtx.lineWidth = 2;
 //        this.pCtx.strokeStyle = 'rgb(0, 0, 0)';
 
 //        this.pCtx.beginPath();
 
 //        var sliceWidth = this.pCanvas.width * 1.0 / bufferLength, // 一个点占多少位置,共有bufferLength个点要绘制
 //                x = 0;          // 绘制点的x轴位置
 
 //        for (var i = 0; i < bufferLength; i++) {
 //          var v = dataArray[i] / 128.0;
 //          var y = v * this.pCanvas.height / 2;
 
 //          if (i === 0) {
 //            // 第一个点
 //            this.pCtx.moveTo(x, y);
 //          } else {
 //            // 剩余的点
 //            this.pCtx.lineTo(x, y);
 //          }
 //          // 依次平移,绘制所有点
 //          x += sliceWidth;
 //        }
 
 //        this.pCtx.lineTo(this.pCanvas.width, this.pCanvas.height / 2);
 //        this.pCtx.stroke();
      },
	  uploadWav(){
		  let file = this.blobToFile(recorder.getWAVBlob(),"1.wav")
		  console.log("开始上传");
		  console.log(file);
		  uni.uploadFile({
		  	header: {
		  		
		  	},
		  	url: uni.getStorageSync('BaseUrl') +
		  		'/file/upload',
		  	// filePath: file,
			file:file,
		  	name: 'file',
		  	formData: {
		  		'user': 'test'
		  	},
		  	success: (uploadFileRes) => {
				console.log('/sys/common/static/scott/pic/' +
		  			JSON.parse(uploadFileRes.data).result);
		  	},
		  	complete: () => {},
		  	fail: (res) => {
		  		console.log(res)
		  	}
		  })
	  }
 
    },
 
  }
</script>
 
<style lang='less' scoped>
 
</style>
  1. 运行
    在这里插入图片描述

  2. 注意,调试环境这里会报错,所以开始解决报错问题:
    报错:error:浏览器不支持getUserMedia !

在这里插入图片描述
这里使用的chrome浏览器测试的,所以我们在chrome浏览器当中输入:

chrome://flags/#unsafely-treat-insecure-origin-as-secure

输入你的本地网址,改为enabled,选择重启浏览器按钮【生产环境当中由于是使用域名进行访问,所以就不会报错。】
在这里插入图片描述
原因及原理:使用js-audio-recorder报浏览器不支持getUserMedia

  1. 调试正常:(大前提你的电脑要求插入耳机/喇叭和麦克风【耳麦一体的需要转换为双插头】否则会报找不到设备)
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

错误指引:

1. NotFoundError : Requested device not found

在这里插入图片描述
原因:没插麦克风
解决:插入麦克风并调整为音频主输入
在这里插入图片描述

2. 手机扫码显示不出来

在这里插入图片描述
原因:使用了localhost:8080作为url
解决:使用ip地址或者域名访问

3. 手机上访问不到或者无法测试(微信扫码也不行)

原因:只有谷歌内核的浏览器可以如此设置,想在手机上测试或使用请下载安卓版的谷歌浏览器或者使用域名访问。


插件和原理解说

插件官网(git)
https://github.com/2fps/recorder

文档
http://recorder.api.zhuyuntao.cn/Recorder/start.html

demo
https://recorder.zhuyuntao.cn/

uniapp文件上传函数解说
https://uniapp.dcloud.net.cn/api/request/network-file.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

uniapp中使用网页录音并上传声音文件(发语音)——js-audio-recorder的使用【伸手党福利】 的相关文章

随机推荐

  • 【AMD、CMD和CommonJS】

    CommonJS规范的特点 对于基本数据类型 属于复制 即会被模块缓存 同时 在另一个模块可以对该模块输出的变量重新赋值 对于复杂数据类型 属于浅拷贝 由于两个模块引用的对象指向同一个内存空间 因此对该模块的值做修改时会影响另一个模块 当使
  • Java异常分类总结

    在Java中 所有的异常都有一个共同的祖先Throwable 可抛出 类 Throwable指定代码中可用异常传播机制通过Java应用程序传输的任何问题的共性 Throwable有两个重要的子类 Exception 异常 和Error 错误
  • LeetCode-1792. 最大平均通过率【堆,优先队列,贪心】

    LeetCode 1792 最大平均通过率 堆 优先队列 贪心 题目描述 解题思路一 优先队列 首先任何一个班级 x y 加入一个聪明的学生之后增加的通过率为diff x 1 y 1 x y 那么对p进行堆排序 每次取最大的即可 解题思路二
  • Excel打开后关闭就马上跳出 Visual c++ Runtime Error R6025

    环境 Win10 专业版 Excel 2016 绿盾加密环境 问题描述 Excel打开后关闭就马上跳出 visual c runtime error R6025 runtime error program c program files m
  • KVM和QEMU

    原文地址 KVM和QEMU 作者 embeddedlwp 目录 1 硬件虚拟化技术背景 2 KVM的内部实现概述 2 1 KVM的抽象对象 2 2 KVM的vcpu 2 3 KVM的IO虚拟化 2 3 1 IO的虚拟化 2 3 2 Virt
  • jdk1.8.191 JVM内存参数 InitialRAMPercentage和MinRAMPercentage

    MaxRAMPercentage InitialRAMPercentage MinRAMPercentage 这三个参数是JDK8U191为适配Docker容器新增的几个参数 类比Xmx Xms 至于 XX InitialRAMFracti
  • 物联网安全概述

    什么是物联网 在你学习有关IPv6的时候 你的老师或许说过 有一天在你的房子每个设备都会有一个IP 物联网基本上就是处理每天的事务 并把它们连接到互联网上 一些常见的物联网设备 如灯光 窗帘 空调 也有像冰箱这样的不太常见的设备 甚至一个卫
  • [sicily] 1003. 相连的1

    声明 原题目转载自中山大学sicily平台 解答部分为原创 Problem 对于一个01矩阵A 求其中有多少片连成一片的1 每个1可以和上下左右的1相连 请为下面的Solution类实现解决这一问题的函数countConnectedOnes
  • 聚合支付行业术语,你get到了吗?

    俗话说 内行看门道外行凑热闹 每一个行业都有它独特的专业术语 对于外行人来说 这些专业术语就跟专有名词一样难懂 支付行业也是一样 因为是近几年的新兴行业 很多人对这一行不懂 甚至一些在支付行业工作的人 对这一行的很多名词概念也很模糊 认知仅
  • 基础篇(二):内存屏障是什么

    目录 前置知识 内存屏障 什么是内存屏障 作用 内存屏障的分类 1 强制读取 刷新主内存的屏障 强制刷新主内存 Load屏障 强制读取主内存 Store屏障 总结 2 禁止指令重排序的屏障 LoadLoad屏障 StoreStore屏障 L
  • 怎么修改游戏内存服务器,修改游戏服务器内存

    修改游戏服务器内存 内容精选 换一换 当您成功创建私有镜像后 镜像的状态为 正常 您可以使用该镜像创建服务器实例或云硬盘 也可以将镜像共享给其他帐号 或者复制镜像到其他区域 私有镜像的生命周期如图1所示 通过华为云创建的ECS服务器默认使用
  • mysql客户端小海豚_MySQL基础

    1 数据库概述 1 1数据的存储方式 第一种存储方式是创建对象 实际上new出来的对象不就是用来存数据的嘛 创建对象就是在堆内存中为对象请求了一个空间 相当于是将对象存入堆内存 第二种方式存文件中 这个在IO流部分我们就是这么处理的 但是缺
  • Python批量改文件名

    对以下路径中的文件名批量修改 文章目录 一 读取指定路径中的文件名 二 正则表达式提取需要保留的部分 1 介绍re库 2 re库中函数的用法 1 re findall 最常用 2 re sub pattern repl string cou
  • 数仓知识点

    传统数仓知识 1 数据仓库分层 ODS 数据准备层 该区为数据仓的准备区 直接输入源数据 如业务库 埋点日志和消息队列等 DWD 数据细节层 该层为业务层和数据层的隔离层 保持和ODS层相同的颗粒度 该层还进行了数据清洗和规范化操作 例如去
  • 阿里巴巴笔试-2020.7.27-第二题 藏宝架

    题目 有个藏宝架有n层 每层的宝物数量不一 每个宝物都有其价值 现在要求拿出m个宝物 并且需要遵守规则 每次只能拿选定层的两端的宝物 要拿出的m个宝物的总价值是各种方案里最大的 输入 第一行是 n 和 m 后面每一行是各层的数据 n m 下
  • WebSocket 基于JAVA Spring boot Spring Colud 的使用

    先上代码再看调试结果 package com qiang user util import com alibaba fastjson JSONObject import org springframework stereotype Comp
  • 软考网络工程师-最新最全小白攻略

    一 前言 最近Beau 博主本人 也是考取了2023年上半年的软考网络工程师 这里也准备给小白们做一些避坑流程 这里附上通过图 二 考前准备 1 报考条件 无 无年龄 资质 学历限制 无需通过软考初级才能报考 是中国守法公民即可报名 2 考
  • webpack 保存文件后自动打包_自动打包插件webpack-dev-server的安装、配置及使用

    1 介绍 webpack dev server插件可以实现Webpack的自动打包编译 这样 就不需要每次修改完代码都重新手动输入webpack打包了 2 安装 在项目的根路径下输入 cnpm i webpack dev server D
  • Python----模块(Module)和包(Package)

    Python 包 包 定义 为了组织好模块 会将多个模块分为包 Python 处理包也是相当方便的 简单来说 包就是文件夹 但该文件夹下必须存在 init py 文件 常见的包结构如下 最简单的情况下 只需要一个空的 init py 文件即
  • uniapp中使用网页录音并上传声音文件(发语音)——js-audio-recorder的使用【伸手党福利】

    uniapp中上传音频只能在app或小程序当中实现 如何使用网页完成语音的录制和上传则成为了困扰前端童鞋的重点 本文着重解决 js audio recorder报 error 浏览器不支持getUserMedia 的问题 js audio