Recorder Documentation

Html5 js 录音 mp3 wav ogg webm amr g711a g711u 格式,支持pc和Android、iOS部分Web浏览器、Hybrid App(提供Android iOS App源码)、微信,提供ASR语音识别转文字 H5版语音通话聊天示例 DTMF编码解码

5,641 stars JavaScript JavaScript 13 files · ~38,100 tokens #amr#asr#audio#browser#dtmf#h5#html#html5
RAW Doc

File: README.md

【源GitHub仓库】 | 【Gitee镜像库】本文档中默认使用github.io部署的链接,如果无法访问,请直接下载仓库源码到本地然后双击打开html文件访问。

:open_book:Recorder用于html5录音

支持在大部分已实现getUserMedia的移动端、PC端浏览器麦克风录音、实时处理,主要包括:Chrome、Firefox、Safari、iOS 14.3+、Android WebView、腾讯Android X5内核(QQ、微信、小程序WebView)、Electron、大部分2021年后更新的Android手机自带浏览器、2024年后更新的Android版UC内核(UC、支付宝、钉钉);不支持:老旧国产手机自带浏览器、老旧iOS(11.0-14.2)上除Safari外的其他任何形式的浏览器。

支持在非浏览器环境中使用部分功能(如nodejs、各种使用js来构建的程序),使用RecordApp可在微信小程序、uni-app中直接录音。

支持对任意MediaStream进行音频录制、实时处理,包括:getUserMedia返回的流、WebRTC中的remote流、audio、video标签的captureStream方法返回的流、自己创建的流 等等。

提供多个插件功能支持,拥有丰富的音频可视化、变速变调处理、语音识别、音频流播放等;搭配上强大的实时处理支持,可用于各种网页应用:从简单的录音,到复杂的实时语音识别(ASR),甚至音频相关的游戏,都能从容应对;提供转码支持,允许将录制的buffers数据或任意pcm数据转码成你需要的格式(参考rec.mock方法)。

主要用于语音录制,因此仅对单声道进行支持(未适配双声道),支持超长时间录音(参考onProcess实时清理释放内存);默认输出mp3格式,另外可选wav、pcm、g711a、g711u、ogg、amr、webm(beta)格式,支持任意格式扩展(前提有相应编码器);使用recorder.mp3.min.js(150kb)即可录制mp3,使用recorder.wav.min.js(25kb)即可录制wav;均支持实时转码和实时传输。

音频文件的上传和播放:可直接使用常规的Audio HTML标签来播放完整的音频文件,参考文档下面的【快速使用】部分,有上传和播放例子;上传了的录音直接将音频链接赋值给audio.src即可播放;本地的blob音频文件可通过URL.createObjectURL来生成本地链接赋值给audio.src即可播放,或者将blob对象直接赋值给audio.srcObject(兼容性没有src高)。实时的音频片段文件播放,可以使用本库自带的BufferStreamPlayer插件来播放,简单高效,或者采用别的途径播放。

如需录音功能定制开发,网站、App、小程序、前端后端开发等需求,请加本文档下面的QQ群,联系群主(即作者),谢谢~

​

Recorder H5 :
[ H5在线测试 ]
[ H5 QuickStart ]
[ H5 vue ]
[ H5 ts ]
[ H5 es module ]
[ 旧版本测试 ]

Recorder App :
[ RecordApp测试 ]
[ App QuickStart ]
[ App vue ]
[ Android、iOS App源码 ]
[ 微信小程序源码 ]
[ uni-app源码 ]

工具集 :
[ Recorder代码运行和静态分发 ]
[ PCM转WAV播放测试和转码 ]
[ 无用户操作测试 ]
[ Can I Use查看浏览器支持情况 ]

手机浏览器扫一扫在线测试

​

Demo片段列表

  1. 【教程】【PCM基础】buffers转pcm、转采样率、改音量、pcm合并转mp3等格式
  2. 【教程】【音频流】【上传】实时转码上传-实时帧回调版
  3. 【教程】【音频流】【上传】实时转码上传-pcm固定帧大小
  4. 【教程】【音频流】【上传】实时转码上传-通用转码旧版
  5. 【教程】【音频流】【播放】实时解码播放音频片段
  6. 【教程】【播放】【可视化】实时录制处理audio、video播放流
  7. 【教程】【ASR】实时语音识别、音频文件转文字-阿里云版
  8. 【Demo库】【格式转换】-mp3等格式解码转成其他格式
  9. 【Demo库】【格式转换】-wav格式转成其他格式
  10. 【Demo库】【格式转换】-amr格式转成其他格式
  11. 【Demo库】【文件合并】-mp3多个片段文件合并
  12. 【Demo库】【文件合并】-wav多个片段文件合并
  13. 【教程】实时多路音频混音
  14. 【教程】变速变调音频转换
  15. 【教程】新录音从老录音接续、或录制中途插入音频
  16. 【教程】DTMF(电话拨号按键信号)解码、编码
  17. 【测试】PCM采样率转换测试
  18. 【Demo库】【信号处理】IIR低通、高通滤波
  19. 【测试】【信号处理】FFT频域分析ECharts频谱曲线图
  20. 【测试】WebM格式解析并提取音频
  21. 【测试】G711、G72X编码和解码播放
  22. 【Demo库】js二进制转换、基础知识-Base64/Hex/Int16Array/ArrayBuffer/Blob
  23. 【测试】音乐合成-用波形函数将歌曲简谱文本转成PCM
  24. 【测试】音频可视化相关插件测试

App Demo

Android Demo App :
下载APK(40kb,删除.zip后缀,
源码)

iOS Demo App :下载源码 自行编译

​

【QQ群】交流与支持

欢迎加QQ群:①群 781036591、②群 748359095、③群 450721519、④群 1027243616,纯小写口令:recorder

​

​

​

​

:open_book:快速使用

你可以通过阅读和运行QuickStart.html文件来快速入门学习,直接将QuickStart.htmlcopy到你的(https、localhost)网站中,无需其他文件,就能正常开始测试了;注意:需要在https、localhost等安全环境下才能进行录音。

https环境搭建最佳实践:建议给自己的域名申请一个泛域名通配符证书(*.xxx.com),然后线上、本地开发均可使用此证书;本地开发环境直接分配一个三级域名(dev.xxx.com、local.xxx.com、192-168-1-123.xxx.com)解析A记录到电脑局域网的IP地址(192.168.1.123、127.0.0.1),方便本地开发跨端调试(本地如何配置https请针对自己的开发环境自行搜索,很容易)。

获取泛域名通配符证书推荐:在线免费申请(ZeroSSL、Let’s Encrypt);不建议自己生成根证书来签发域名证书,一个是流程复杂,每个设备均要导入根证书,致命的是很多现代浏览器不再信任用户目录下导入的根证书(Android)。

如果必须http访问,Chrome中可尝试打开chrome://flags/#unsafely-treat-insecure-origin-as-secure,启用Insecure origins treated as secure,把你的地址含端口配置进去,然后重启浏览器。

【1】加载框架

Recorder的所有js文件均为手动引入(内部不会自动引用),因此未被你引入的文件均可删除来精简源码大小。

方式一:使用script标签引入

在需要录音功能的页面引入压缩好的recorder.xxx.min.js文件即可(CDN:JsDelivr、unpkg)

html
<script src="recorder.mp3.min.js"></script> <!--已包含recorder-core和mp3格式支持, CDN不稳定仅供测试: https://cdn.jsdelivr.net/gh/xiangyuecn/Recorder@latest/recorder.mp3.min.js-->

或者直接使用源码(src内的为源码、dist内的为压缩后的),可以引用src目录中的recorder-core.js+相应类型的实现文件,比如要mp3录音:

html
<script src="src/recorder-core.js"></script> <!--必须引入的录音核心,CDN不稳定仅供测试: https://cdn.jsdelivr.net/gh/xiangyuecn/Recorder@latest/src/recorder-core.js-->

<script src="src/engine/mp3.js"></script> <!--相应格式支持文件;如果需要多个格式支持,把这些格式的编码引擎js文件放到后面统统加载进来即可-->
<script src="src/engine/mp3-engine.js"></script> <!--如果此格式有额外的编码引擎(*-engine.js)的话,必须要加上-->

<script src="src/extensions/waveview.js"></script>  <!--可选的插件支持项,把需要的插件按需引入进来即可-->

方式二:通过import/require引入

通过 npm 进行安装 npm install recorder-core --registry=https://registry.npmmirror.com/ ,如果直接clone的源码下面文件路径调整一下即可

javascript
//必须引入的核心,换成require也是一样的。注意:recorder-core会自动往window下挂载名称为Recorder对象,全局可调用window.Recorder,也许可自行调整相关源码清除全局污染
import Recorder from 'recorder-core' //注意如果未引用Recorder变量,可能编译时会被优化删除(如vue3 tree-shaking),请改成 import 'recorder-core',或随便调用一下 Recorder.a=1 保证强引用
    //import Recorder from 'recorder-core/src/recorder-core.js.esm.js' //ES Module内请用此方式

//引入相应格式支持文件;如果需要多个格式支持,把这些格式的编码引擎js文件放到后面统统引入进来即可
import 'recorder-core/src/engine/mp3'
import 'recorder-core/src/engine/mp3-engine' //如果此格式有额外的编码引擎(*-engine.js)的话,必须要加上

//以上三个也可以合并使用压缩好的recorder.xxx.min.js
//比如 import Recorder from 'recorder-core/recorder.mp3.min' //已包含recorder-core和mp3格式支持

//可选的插件支持项,把需要的插件按需引入进来即可
import 'recorder-core/src/extensions/waveview'

//ts import 提示:npm包内已自带了.d.ts声明文件(不过是any类型)

【2】调用录音,播放结果

​这里假设只录3秒,录完后立即播放,在线编辑运行此代码>>。录音结束后得到的是Blob二进制文件对象,可以下载保存成文件、用FileReader读取成ArrayBuffer或者Base64给js处理,或者参考下一节上传示例直接上传。

javascript
//简单控制台直接测试方法:在任意(无CSP限制)页面内加载需要的js,加载成功后再执行一次本代码立即会有效果
//①加载Recorder+mp3:await import("https://unpkg.com/recorder-core/recorder.mp3.min.js"); console.log("import ok")
//②可视化插件和显示:await import("https://unpkg.com/recorder-core/src/extensions/waveview.js"); console.log("import ok"); div=document.createElement("div");div.innerHTML='<div style="height:100px;width:300px;" class="recwave"></div>';document.body.prepend(div);

var rec,processTime=0,clearBufferIdx=0,wave;
/**调用open打开录音请求好录音权限**/
var recOpen=function(success){//一般在显示出录音按钮或相关的录音界面时进行此方法调用,后面用户点击开始录音时就能畅通无阻了
    rec=Recorder({ //本配置参数请参考下面的文档,有详细介绍
        type:"mp3",sampleRate:16000,bitRate:16 //mp3格式,指定采样率hz、比特率kbps,其他参数使用默认配置;注意:是数字的参数必须提供数字,不要用字符串;需要使用的type类型,需提前把格式支持文件加载进来,比如使用wav格式需要提前加载wav.js编码引擎
        ,onProcess:function(buffers,powerLevel,bufferDuration,bufferSampleRate,newBufferIdx,asyncEnd){
            //录音实时回调,大约1秒调用12次本回调,buffers为开始到现在的所有录音pcm数据块(16位小端LE)
            //可利用extensions/sonic.js插件实时变速变调,此插件计算量巨大,onProcess需要返回true开启异步模式
            //可实时上传(发送)数据,配合Recorder.SampleData方法,将buffers中的新数据连续的转换成pcm上传,或使用mock方法将新数据连续的转码成其他格式上传,可以参考文档里面的:Demo片段列表 -> 实时转码并上传-通用版;基于本功能可以做到:实时转发数据、实时保存数据、实时语音识别(ASR)等
            processTime=Date.now();

            //可实时绘制波形(extensions目录内的waveview.js、wavesurfer.view.js、frequency.histogram.view.js插件功能)
            wave&&wave.input(buffers[buffers.length-1],powerLevel,bufferSampleRate);

            /*实时释放清理内存,用于支持长时间录音;在指定了有效的type时,编码器内部可能还会有其他缓冲,必须同时提供takeoffEncodeChunk才能清理内存,否则type需要提供unknown格式来阻止编码器内部缓冲
            //if(this.clearBufferIdx>newBufferIdx){ this.clearBufferIdx=0 } //变量改到this里面时,重新录音了,这样写可以重置this环境
            for(var i=clearBufferIdx||0;i<newBufferIdx;i++) buffers[i]=null;
            clearBufferIdx=newBufferIdx; */
        }
        ,takeoffEncodeChunk: "不设置"?null: function(chunkBytes){
            //实时编码环境下接管编码器输出,类似于onFrameRecorded,当生成了一块音频数据就会回调
            //chunkBytes为指定录音格式的二进制数据Uint8Array,提供此回调后编码器内将不会保存数据(调用stop方法获得的音频数据长度将为0),需自行保存数据
        }
    });

    rec.open(function(){//打开麦克风授权获得相关资源
        //rec.start() 此处可以立即开始录音,但不建议这样编写,因为open是一个延迟漫长的操作,通过两次用户操作来分别调用open和start是推荐的最佳流程

        //创建可视化,指定一个要显示的div
        if(Recorder.WaveView)wave=Recorder.WaveView({elem:".recwave"});
        success&&success();
    },function(msg,isUserNotAllow){//用户拒绝未授权或不支持
        console.log((isUserNotAllow?"UserNotAllow,":"")+"无法录音:"+msg);
    });
};

/**开始录音**/
function recStart(){//打开了录音后才能进行start、stop调用
    rec.start();
    
    //【稳如老狗WDT】可选的,监控是否在正常录音有onProcess回调,如果长时间没有回调就代表录音不正常
    var wdt=rec.watchDogTimer=setInterval(function(){
        if(!rec || wdt!=rec.watchDogTimer){ clearInterval(wdt); return } //sync
        if(Date.now()<rec.wdtPauseT) return; //如果暂停录音了就不检测:puase时赋值rec.wdtPauseT=Date.now()*2(永不监控),resume时赋值rec.wdtPauseT=Date.now()+1000(1秒后再监控)
        if(Date.now()-(processTime||startTime)>1500){ clearInterval(wdt);
            console.error(processTime?"录音被中断":"录音未能正常开始");
            // ... 错误处理,关闭录音,提醒用户
        }
    },1000);
    var startTime=Date.now(); rec.wdtPauseT=0; processTime=0;
};

/**结束录音**/
function recStop(){
    rec.watchDogTimer=0; //停止监控onProcess超时
    rec.stop(function(blob,duration){
        
        //简单利用URL生成本地文件地址,注意不用了时需要revokeObjectURL,否则霸占内存
        //此地址只能本地使用,比如赋值给audio.src进行播放,赋值给a.href然后a.click()进行下载(a需提供download="xxx.mp3"属性)
        var localUrl=(window.URL||webkitURL).createObjectURL(blob);
        console.log(blob,localUrl,"时长:"+duration+"ms");
        rec.close();//释放录音资源,当然可以不释放,后面可以连续调用start;但不释放时系统或浏览器会一直提示在录音,最佳操作是录完就close掉
        rec=null;
        
        //已经拿到blob文件对象想干嘛就干嘛:立即播放、上传、下载保存
        
        /*** 【立即播放例子】 ***/
        var audio=document.createElement("audio");
        document.body.prepend(audio);
        audio.controls=true;
        audio.src=localUrl;
        audio.play();
    },function(msg){
        console.log("录音失败:"+msg);
        rec.close();//可以通过stop方法的第3个参数来自动调用close
        rec=null;
    });
};


//这里假设立即运行,只录3秒,录完后立即播放,本段代码copy到控制台内可直接运行
recOpen(function(){
    recStart();
    setTimeout(recStop,3000);
});

​

​

【附】录音上传示例

javascript
var TestApi="/test_request";//用来在控制台network中能看到请求数据,测试的请求结果无关紧要
var rec=Recorder();rec.open(function(){rec.start();setTimeout(function(){rec.stop(function(blob,duration){
//-----↓↓↓以下才是主要代码↓↓↓-------

//本例子假设使用jQuery封装的请求方式,实际使用中自行调整为自己的请求方式
//录音结束时拿到了blob文件对象,可以用FileReader读取出内容,或者用FormData上传
var api=TestApi;

/***方式一:将blob文件转成base64纯文本编码,使用普通application/x-www-form-urlencoded表单上传***/
var reader=new FileReader();
reader.onloadend=function(){
    $.ajax({
        url:api //上传接口地址
        ,type:"POST"
        ,data:{
            mime:blob.type //告诉后端,这个录音是什么格式的,可能前后端都固定的mp3可以不用写
            ,upfile_b64:(/.+;\s*base64\s*,\s*(.+)$/i.exec(reader.result)||[])[1] //录音文件内容,后端进行base64解码成二进制
            //...其他表单参数
        }
        ,success:function(v){
            console.log("上传成功",v);
        }
        ,error:function(s){
            console.error("上传失败",s);
        }
    });
};
reader.readAsDataURL(blob);

/***方式二:使用FormData用multipart/form-data表单上传文件***/
var form=new FormData();
form.append("upfile",blob,"recorder.mp3"); //和普通form表单并无二致,后端接收到upfile参数的文件,文件名为recorder.mp3
//...其他表单参数
$.ajax({
    url:api //上传接口地址
    ,type:"POST"
    ,contentType:false //让xhr自动处理Content-Type header,multipart/form-data需要生成随机的boundary
    ,processData:false //不要处理data,让xhr自动处理
    ,data:form
    ,success:function(v){
        console.log("上传成功",v);
    }
    ,error:function(s){
        console.error("上传失败",s);
    }
});

//-----↑↑↑以上才是主要代码↑↑↑-------
},function(msg){console.log("录音失败:"+msg);});},3000);},function(msg){console.log("无法录音:"+msg);});

​

【附】js中的二进制基础知识:Uint8Array Int16Array ArrayBuffer Blob/File

更多js二进制数据基础知识请阅读:js二进制转换-Base64/Hex/Int16Array/ArrayBuffer/Blob

javascript
//XMLHttpRequest、WebSocket等一般都能直接send(TypedArray/ArrayBuffer/Blob)到服务器,服务器会收到二进制数据
//TypedArray/ArrayBuffer在绝大部分js运行环境中都支持,Blob只在浏览器中支持

//Recorder的buffers是pcm数组,单个pcm是Int16Array(TypedArray子类)
var arrayBuffer=pcm.buffer; //pcm的Int16Array转ArrayBuffer
var pcm=new Int16Array(arrayBuffer); //pcm的ArrayBuffer转成Int16Array(共享内存,arrayBuffer.slice(0)截取新的不共享)

//Recorder的takeoffEncodeChunk得到的chunkBytes是Uint8Array(TypedArray子类,类似于无符号byte[])
var arrayBuffer=chunkBytes.buffer; //chunkBytes的Uint8Array转ArrayBuffer
var chunkBytes=new Uint8Array(arrayBuffer); //任意ArrayBuffer均能转成Uint8Array

//ArrayBuffer 与 Blob/File 可以相互转换,File是Blob的一个特例(子类)
var blob=new Blob([arrayBuffer],{type:"audio/mp3"});
var file=new File([arrayBuffer],"xxx.mp3");
var arrayBuffer=reader.result; //用FileReader的readAsArrayBuffer方法读取 Blob/File 成ArrayBuffer

//多个pcm二进制拼接合并成一个,下面Int16Array换成Uint8Array等其他TypedArray也是一样的
var pcmList=[ new Int16Array([1,2]), new Int16Array([3,4,5,6]) ];
var size=0;for(var i=0;i<pcmList.length;i++) size+=pcmList[i].length;
var pcm=new Int16Array(size); //list是Uint8Array就改成new Uint8Array(size);
for(var i=0,offset=0;i<pcmList.length;i++){
    pcm.set(pcmList[i],offset); //循环写入到前一个的后面,完成拼接
    offset+=pcmList[i].length;
};
//pcm可使用rec.mock方法转码成其他任意格式,比如mp3

//二进制封装包头,比如要发送特定封包格式的数据包
var bytes=new Uint8Array(pcm.buffer); //需要封装的音频二进制数据
var header=new Uint8Array(2); //假设2字节包头
header[0]=0xEF; header[1]=0x01; //填充任意需要的内容
var packet=new Uint8Array(header.length+bytes.length); //拼接
packet.set(header,0);
packet.set(bytes,header.length); //拼接完成

​

​

【附】Android App - WebView中录音示例

app-support-sample/demo_android目录中提供了Android测试项目源码(如果不想自己打包可以用打包好的apk来测试,demo_android目录下的app-debug.apk.zip,自行去掉.zip后缀)。

在Android App WebView中使用本库来H5录音,需要在App源码中实现以下几部分:

  1. 在AndroidManifest.xml声明需要用到的两个权限,第二个也必须的
xml
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
  1. WebView的WebChromeClient中实现onPermissionRequest网页授权请求

如果未实现此方法,WebView默认会拒绝H5录音权限;onPermissionRequest中需要先获取App的系统录音权限(Activity里面必须先用this.checkSelfPermission检测权限,否则直接this.requestPermissions会造成WebView触发touchcancel打断长按),然后再grant网页权限,可参考Android Demo中的MainActivity.java中的实现代码。

注:如果应用的腾讯X5内核,可能还须提供android.permission.CAMERA权限,和调用webView.setWebChromeClientExtension来重写X5的IX5WebChromeClientExtension.onPermissionRequest方法来进行权限处理

  1. 如需后台录音,还需要实现Android后台录音保活服务

自Android 9开始,为了保护用户隐私,锁屏或进入后台一段时间后App可能会被禁止访问麦克风、摄像头等功能,导致无法录音、或录音数据全部是静音,因此需要使用保活机制才能在后台录音,详细请参考app-support-sample/demo_android,里面专门有一个章节讲解保活。

​

【附】iOS App - WebView中录音示例

app-support-sample/demo_ios目录中提供了iOS测试项目源码(需自行用xcode编译运行)。

在iOS App WebView中使用本库来录音,需要在App源码 Info.plist 中声明使用麦克风 NSMicrophoneUsageDescription,无需其他处理,WebView会自己处理好录音权限;注意:iOS App需要在项目Background Modes中勾选Audio才能在后台保持录音,不然App切到后台后立马会停止录音。

iOS 14.3+以上版本才支持WebView中进行H5录音。

iOS 15+以上版本提供了静默授权支持,需要WKWebView的uiDelegate实现WKUIDelegate requestMediaCapturePermissionFor接口,可参考iOS Demo中的MainView.swift中的代码;如果未实现,会导致H5录音每次打开页面后第一次录音时、或长时间无操作再打开录音时均会弹出录音权限对话框。

iOS 11.0-14.2:纯粹的H5录音在iOS WebView中是不支持的,需要有Native层的支持,具体参考RecordApp中的app-support-sample/demo_ios测试项目源码。

​

【附】微信小程序中录音集成参考

RecordApp支持在微信小程序中直接进行录音,请参考小程序Demo项目:miniProgram-wx。

[不推荐] 如果仅使用Recorder的话,将只能在H5页面中使用,然后在小程序的web-view中显示这个H5,和普通浏览器没有区别。

​

【附】Electron程序中录音集成参考

在Electron BrowserWindow中使用本库来录音,需要程序本身先获得录音权限;在调用open打开录音前,需要先到主进程使用 systemPreferences.getMediaAccessStatus('microphone') 判断程序是否有录音权限,如果是未申请权限,需要调用 systemPreferences.askForMediaAccess('microphone') 来请求权限;程序有权限后,即可和普通网页一样正常的录音。

​

【附】uni-app集成参考

RecordApp支持在uni-app中直接进行录音,支持编译成:H5、Android App、iOS App、微信小程序,请参考Demo项目和文档:demo_UniApp,对应的组件可到DCloud 插件市场下载。

[不推荐] 如果仅使用Recorder的话,因为uni-app中的renderjs是直接运行在视图层WebView中的,因此可以通过在renderjs中加载Recorder来进行录音;此方法支持App、H5,但不支持小程序。同时需要注意在开发App平台的代码时,需在调用rec.open前,在原生层获取到录音权限;和上面的Android和iOS一样先配置好录音权限声明,再调用权限请求接口(iOS的WebView会自动处理可以不请求),在逻辑层中编写js权限处理代码。

​

​

​

​

​

​

:open_book:需要注意的细节和问题

本库期待的使用场景是语音录制,因此音质只要不比高品质的感觉差太多就行;1分钟的语音进行编码是很快的,但如果录制超长的录音,比如10分钟以上,不同类型格式的编码可能会花费比较长的时间,大部分编码器支持边录边转码(Worker),因此录音时转码速度极快。另外未找到双声道语音录制存在的意义(翻倍录音数据大小,并且拉低音质),因此特意仅对单声道进行支持。

浏览器Audio Media兼容性mp3最好,wav还行,其他要么不支持播放,要么不支持编码;因此本库最佳推荐使用mp3、wav格式,代码也是优先照顾这两种格式。

getUserMedia的浏览器支持情况和兼容性,可到Can I use查看。

留意中途来电话:在移动端录音时,如果录音中途来电话,或者通话过程中打开录音,是不一定能进行录音的;经过简单测试发现,iOS上Safari将暂停返回音频数据,直到通话结束才开始继续有音频数据返回;小米上Chrome不管是来电还是通话中开始录音都能对麦克风输入的声音进行录音;只是简单测试,更多机器和浏览器并未做测试,不过整体上来看来电话或通话中进行录音的可行性并不理想,也不赞成在这种过程中进行录音;但只要通话结束后录音还是会正常进行,影响基本不大。

录音时对播放音频的影响:仅在移动端,如果录音配置中未禁用降噪+回声消除(浏览器默认开启降噪+回声),打开录音后,如果同时播放音频,此时系统播放音量可能会变得很小,关闭录音后一般可恢复音量;PC上 和 禁用降噪+回声消除后 似乎无此影响,但iOS上如果禁用又可能会导致无法正常录音,详细请阅读配置文档中的audioTrackSet参数说明。

移动端锁屏录音:手机锁屏后浏览器的运行状态是一个玄学,是否能录音不可控;不同手机、甚至同一手机在不同状态下,有可能能录又有可能不能录,且无法检测;可以调用 navigator.wakeLock 来阻止手机自动锁屏,不支持的直接简单粗暴的 循环+静音 播放一段视频,来阻止锁屏,就是有点费电,具体实现可参考H5在线测试页面内的wakeLockClick方法。

特别注:如果在iframe里面调用的录音功能,并且和上层的网页是不同的域(跨域了),如果未设置相应策略,权限永远是被拒绝的,参考此处。另外如果要在非跨域的iframe里面使用,最佳实践应该是让window.top去加载Recorder(异步加载js),iframe里面使用top.Recorder,免得各种莫名其妙。

已知问题

此处已清除7个已知问题,大部分无法解决的问题会随着时间消失;问题主要集中在iOS部分版本上(不同系统版本问题不重样),好在这玩意能更新

2025-03-06 已知现有版本的Recorder.SampleData在部分条件下,录音实时转换采样率时,如果两个采样率不是倍数关系时(44100和16000互转),内部的每次转换会引入杂音,大部分情况下并不明显,有些蓝牙耳机录制的44100采样率数据(实际只有24000),经过转换后杂音会非常明显;后续版本会尝试修正这个转换错误。 260708版本已修复。

2024-09-09 #230 已知open源码中的trackSet[sampleRateTxt]=...强制设置采样率会导致部分设备无法正常打开麦克风,导致录音无声(或无法使用蓝牙等外设),需要修改源码去掉这行源码,同时设置Recorder.ConnectEnableWebM=false, 241020版本开始已移除此代码进行了修复,并增强了MediaRecorder的支持。

2024-09-09 部分iOS上(含新系统)第二次打开录音后无法正常开始录音,在开始录音前调用一次Recorder.Destroy()清除掉已打开的AudioContext可能会恢复正常,241020版本开始将不再保留打开状态的Recorder.Ctx。

2023-12-06 iOS上反复弹出的权限弹框:据QQ群内1806152243开发者反馈,进入页面打开一次录音并关闭后,如果没有在页面上进行任何用户交互操作(点击、触摸、滑动等),大约35秒左右之后,重新打开录音时,浏览器将会再次弹出录音权限对话框;iOS Safari上稳定复现;这是浏览器自己的行为,js无法控制,设计交互逻辑时应当注意。

2023-02-22 iPhone 14:有部分开发者反馈iPhone14上关闭录音后再次打开录音,会出现无法录音的情况,目前并不清楚是只有iPhone14上有问题,还是iOS16均有问题;估计是新的WebKit改了相关源码印度阿三没有测试,js没办法解决此问题,静候iOS更新,也许下一个系统更新就自动修复了;建议针对iOS环境,全局只open一次,不要close,挂在那里录音,可减少iOS系统问题带来的影响(负优化+耗电)。 2023-7-1,此问题已修复,原因出在AudioContext上,iOS新版本上似乎不能共用一个AudioContext(新版本每次open均会创建新的AudioContext),并且iOS上AudioContext的resume行为和其他浏览器不相同,如果不是通过用户操作(触摸、点击等)进行调用,将无法resume,参考 ztest_AudioContext_resume.html 测试用例。

2020-04-26 Safari Bug:据QQ群内1048506792、190451148开发者反馈研究发现,IOS ?-13.X Safari内打开录音后,如果切换到了其他标签、或其他App并且播放了任何声音,此时将会中断已打开的录音(系统级的?),切换回正在录音的页面,这个页面的录音功能将会彻底失效,并且刷新也无法恢复录音;表现为关闭录音后再次打开录音,能够正常获得权限,但浏览器返回的采集到的音频为静默的PCM,此时地址栏也并未显示出麦克风图标,刷新这个标签也也是一样不能正常获得录音,只有关掉此标签新打开页面才可正常录音。如果打开录音后关闭了录音,然后切换到其他标签或App播放声音,然后返回录音页面,不会出现此问题。此为Safari的底层Bug。使用长按录音类似的用户交互可大幅度避免踩到这坨翔。

​

​

​

​

​

​

:open_book:方法文档

【构造】rec=Recorder(set)

构造函数,拿到Recorder的实例,然后可以进行请求获取麦克风权限和录音。

set参数为配置对象,默认配置值如下:

javascript
set={
    type:"mp3" //输出类型:mp3,wav等,使用一个类型前需要先引入对应的编码引擎
    ,bitRate:16 //比特率,必须是数字 wav(位):16、8,MP3(单位kbps):8kbps时文件大小1k/s,16kbps 2k/s,录音文件很小
    
    ,sampleRate:16000 //采样率,必须是数字,wav格式(8位)文件大小=sampleRate*时间;mp3此项对低比特率文件大小有影响,高比特率几乎无影响。
                //wav任意值,mp3取值范围:48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000
    
    ,onProcess:NOOP //接收到录音数据时的回调函数:fn(buffers,powerLevel,bufferDuration,bufferSampleRate,newBufferIdx,asyncEnd)
                //返回值:onProcess如果返回true代表开启异步模式,在某些大量运算的场合异步是必须的,必须在异步处理完成时调用asyncEnd(不能真异步时需用setTimeout包裹);返回其他值或者不返回为同步模式(需避免在回调内执行耗时逻辑);如果开启异步模式,在onProcess执行后新增的buffer会全部替换成空数组,因此本回调开头应立即将newBufferIdx到本次回调结尾位置的buffer全部保存到另外一个数组内,处理完成后写回buffers中本次回调的结尾位置。
                //buffers=[[Int16,...],...]:缓冲的PCM数据块(16位小端LE),为从开始录音到现在的所有pcm片段,每次回调可能增加0-n个不定量的pcm片段。
                    //注意:buffers数据的采样率为bufferSampleRate,它和set.sampleRate不一定相同,可能为浏览器提供的原始采样率rec.srcSampleRate,也可能为已转换好的采样率set.sampleRate;如需浏览器原始采样率的数据,请使用rec.buffers原始数据,而不是本回调的参数;如需明确和set.sampleRate完全相同采样率的数据,请在onProcess中自行连续调用采样率转换函数Recorder.SampleData(),配合mock方法可实现实时转码和压缩语音传输;修改或替换buffers内的数据将会改变最终生成的音频内容(注意不能改变第一维数组长度),比如简单有限的实现实时静音、降噪、混音等处理,详细参考下面的rec.buffers
                //powerLevel:当前缓冲的音量级别0-100。
                //bufferDuration:已缓冲时长。
                //bufferSampleRate:buffers缓存数据的采样率(当type支持边录边转码(Worker)时,此采样率和设置的采样率相同,否则不一定相同)。
                //newBufferIdx:本次回调新增的buffer起始索引。
                //asyncEnd:fn() 如果onProcess是异步的(返回值为true时),处理完成时需要调用此回调,如果不是异步的请忽略此参数,此方法回调时必须是真异步(不能真异步时需用setTimeout包裹)。
                //如果需要绘制波形之类功能,需要实现此方法即可,使用以计算好的powerLevel可以实现音量大小的直观展示,使用buffers可以达到更高级效果
                //如果需要实时上传(发送)之类的,可以配合Recorder.SampleData方法,将buffers中的新数据连续的转换成pcm,或使用mock方法将新数据连续的转码成其他格式,可以参考文档里面的:Demo片段列表 -> 实时转码并上传-通用版;基于本功能可以做到:实时转发数据、实时保存数据、实时语音识别(ASR)等
                //如果需要长时间录音,比如超过1小时,需要在onProcess内实时释放清理内存,参考实时上传中有释放代码,需要自行实现数据的保存
    
    //*******高级设置******
        //,sourceStream:MediaStream Object
                //可选直接提供一个媒体流,从这个流中录制、实时处理音频数据(当前Recorder实例独享此流);不提供时为普通的麦克风录音,由getUserMedia提供音频流(所有Recorder实例共享同一个流)
                //比如:audio、video标签dom节点的captureStream方法(实验特性,不同浏览器支持程度不高)返回的流;WebRTC中的remote流;自己创建的流等
                //注意:流内必须至少存在一条音轨(Audio Track),比如audio标签必须等待到可以开始播放后才会有音轨,否则open会失败
                //注意:在rec.close时不会主动关闭此流,需自行管理关闭

        //,runningContext:AudioContext
                //可选提供一个state为running状态的AudioContext对象(ctx);默认会在rec.open时自动创建一个新的ctx,无用户操作(触摸、点击等)时调用rec.open的ctx.state可能为suspended,会在rec.start时尝试进行ctx.resume,如果也无用户操作ctx.resume可能不会恢复成running状态(目前仅iOS上有此兼容性问题),导致无法去读取媒体流,这时请提前在用户操作时调用Recorder.GetContext(true)来得到一个running状态AudioContext(用完需调用CloseNewCtx(ctx)关闭)

        /*,audioTrackSet:{
             deviceId:"",groupId:"" //指定设备的麦克风,通过navigator.mediaDevices.enumerateDevices拉取设备列表,其中kind为audioinput的是麦克风
            ,echoCancellation:true //回声消除(AEC)开关,不设置时由浏览器控制(一般为默认自动打开),设为true明确打开,设为false明确关闭
            ,noiseSuppression:true //降噪(ANS)开关,取值和回声消除开关一样
            ,autoGainControl:true //自动增益(AGC)开关,取值和回声消除开关一样
        }*/
                //普通麦克风录音时getUserMedia方法的audio配置参数;注意:不同浏览器的支持程度不同,提供的任何配置值都不一定会生效
                //回声消除、降噪开关这些参数浏览器一般默认自动打开,移动端未明确禁用时可能会降低系统播放音量(关闭录音后可恢复)和仅提供16k采样率的音频流(不需要回声消除时可明确配置成禁用来获得48k高音质的流)
                //由于麦克风是全局共享的,所以新配置后需要close掉以前的再重新open
                //同样可配置videoTrackSet,更多参考: https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints

        //,disableEnvInFix:false 内部参数,禁用设备卡顿时音频输入丢失补偿功能,如果不清楚作用请勿随意使用

        //,allowUpsample:false 当源采样率this.srcSampleRate<set.sampleRate(如set=48000但源只有44100)时是否要提升采样率,260708版本新增
            //默认不允许,同时set.sampleRate会被重设为源采样率;比如用最高音质录,但实际只取16k的pcm,默认false不影响音质;如果确实需要提升请设为true
            //提升采样率的音质依赖重采样算法和计算复杂度,内置的SampleData+IIRFilter会引入轻微杂音

        //,engine_mp3_lowpassfreq:0 mp3专用配置,16kbps时配置成-1声音不沉闷可提升清晰度,但会增加噪声,详细请阅读文档中mp3编码器说明

        //,takeoffEncodeChunk:NOOP //fn(chunkBytes) chunkBytes=[Uint8,...]:实时编码环境下接管编码器输出,类似于onFrameRecorded,当编码器实时编码出一块有效的二进制音频数据时实时回调此方法;参数为二进制的Uint8Array,就是编码出来的音频数据片段,所有的chunkBytes拼接在一起即为完整音频。本实现的想法最初由QQ2543775048提出。
                //当提供此回调方法时,将接管编码器的数据输出,编码器内部将放弃存储生成的音频数据;如果当前编码器或环境不支持实时编码处理,将在open时直接走fail逻辑
                //因此提供此回调后调用stop方法将无法获得有效的音频数据,因为编码器内没有音频数据,因此stop时返回的blob将是一个字节长度为0的blob
                //大部分录音格式编码器都支持实时编码(边录边转码),比如mp3格式:会实时的将编码出来的mp3片段通过此方法回调,所有的chunkBytes拼接到一起即为完整的mp3,此种拼接的结果比mock方法实时生成的音质更加,因为天然避免了首尾的静默
                //不支持实时编码的录音格式不可以提供此回调(wav格式不支持,因为wav文件头中需要提供文件最终长度),提供了将在open时直接走fail逻辑
}

注意:set内是数字的明确传数字,不要传字符串之类的导致不可预测的异常,其他有配置的地方也是一样(感谢[email protected]19-01-10发的反馈邮件)。

注:如果录音结束后生成的音频文件的比特率和采样率和set中的不同,将会把set中的bitRate、sampleRate更新成音频文件的。

【方法】rec.open(success,fail)

普通的麦克风录音时:请求打开录音资源,如果浏览器不支持录音、用户拒绝麦克风权限、或者非安全环境(非https、file等)将会调用fail;打开后需要调用close来关闭,因为浏览器或设备的系统可能会显示正在录音。

直接提供的流set.sourceStream时:会连接上此流进行录制,打开后同样需要调用close来断开连接,但不会主动关闭此流,需自行管理此sourceStream的关闭。

success=fn();

fail=fn(errMsg,isUserNotAllow); 如果是用户主动拒绝的录音权限,除了有错误消息外,isUserNotAllow=true,方便程序中做不同的提示,提升用户主动授权概率

注意:此方法回调是可能是同步的(异常、或者已持有资源时)也可能是异步的(浏览器弹出权限请求时);一般使用时打开,用完立即关闭;可重复调用,可用来测试是否能录音;open和start至少有一个应当在用户操作(触摸、点击等)下进行调用,原因参考runningContext配置。

另外:普通的麦克风录音时,因为此方法会调起用户授权请求,如果仅仅想知道浏览器是否支持录音(比如:如果浏览器不支持就走另外一套录音方案),应使用Recorder.Support()方法。

【方法】rec.close(success)

关闭释放录音资源,释放完成后会调用success()回调。如果正在录音或者stop调用未完成前调用了close将会强制终止当前录音。

注意:普通的麦克风录音时(所有Recorder实例共享同一个流),如果创建了多个Recorder对象并且调用了open(应避免同时有多个对象进行了open),只有最后一个新建的才有权限进行实际的资源释放(和多个对象close调用顺序无关),浏览器或设备的系统才会不再显示正在录音的提示;直接提供的流 set.sourceStream 无此问题(当前Recorder实例独享此流,会直接释放掉录制此流的相关资源),但不会主动关闭此流,需自行管理此sourceStream的关闭。

【方法】rec.start()

开始录音,需先调用open;未close之前可以反复进行调用开始新的录音。注意:open和start至少有一个应当在用户操作(触摸、点击等)下进行调用,原因参考runningContext配置。

只要open成功后,调用此方法是安全的,如果未open强行调用导致的内部错误将不会有任何提示,stop时自然能得到错误;另外open操作可能需要花费比较长时间,如果中途调用了stop,open完成时(同步)的任何start调用将会被自动阻止,也是不会有提示的。

【方法】rec.stop(success,fail,autoClose)

结束录音并返回录音数据blob文件对象,拿到blob文件对象就可以为所欲为了,不限于立即播放、上传、下载保存。blob可以用URL.createObjectURL生成本地链接赋值给audio.src进行播放,赋值给a.href然后a.click()进行下载(a需提供download="xxx.mp3"属性);或用XMLHttpRequest+FormData、WebSocket直接发送到服务器,或者用FileReader读取成ArrayBuffer或者Base64给js处理。

javascript
success(blob,duration,mime) //录音成功回调
    blob:录音二进制文件数据audio/mp3|wav...格式;
            默认是Blob对象,可设置rec.dataType="arraybuffer"改成ArrayBuffer(或全局设置Recorder.DefaultDataType="arraybuffer")
    duration:录音时长,单位毫秒
    mime:"auido/mp3" blob数据的类型,方便ArrayBuffer时区分类型

fail(errMsg) //录音出错回调

autoClose //可选,是否自动调用`close`,默认为`false`不调用

注意:当创建rec时提供了takeoffEncodeChunk后,你需要自行实时保存录音文件数据,因此stop时返回的blob的长度将为0字节。

提示:stop时会进行音频编码,根据类型的不同音频编码花费的时间也不相同。对于支持边录边转码(Worker)的类型,将极速完成编码并回调;对于不支持的10几秒录音花费2秒左右算是正常,但内部采用了分段编码+setTimeout来处理,界面卡顿不明显。

【方法】rec.pause()

暂停录音。

【方法】rec.resume()

恢复继续录音。

【属性】rec.buffers

此数据为从开始录音到现在为止的所有已缓冲的PCM片段列表(16位小端LE),buffers = [[Int16,...],...] 为二维数组;在没有边录边转码的支持时(mock调用、非mp3等),录音stop时会使用此完整数据进行转码成指定的格式。

rec.buffers中的PCM数据为浏览器采集的原始音频数据,采样率为浏览器提供的原始采样率rec.srcSampleRate;在rec.set.onProcess回调中buffers参数就是此数据或者此数据重新采样后的新数据;修改或替换onProcess回调中buffers参数可以改变最终生成的音频内容,但修改rec.buffers不一定会有效,因此你可以在onProcess中修改或替换buffers参数里面的内容,注意只能修改或替换上次回调以来新增的buffer(不允许修改已处理过的,不允许增删第一维数组,允许将第二维数组任意修改替换成空数组也可以);以此可以简单有限的实现实时静音、降噪、混音等处理。

如果你需要长时间实时录音(如长时间语音通话),并且不需要得到最终完整编码的音频文件:

  1. 未提供set.takeoffEncodeChunk时,Recorder初始化时应当使用一个未知的类型进行初始化(如: type:"unknown",仅仅用于初始化而已,实时转码可以手动转成有效格式,因为有效格式可能内部还有其他类型的缓冲,unknown类型onProcess buffers和rec.buffers是同一个数组);提供set.takeoffEncodeChunk接管了编码器实时输出时,无需特殊处理,因为编码器内部将不会使用缓冲;
  2. 实时在onProcess中修改buffers参数数组,可以只保留最后两个元素,其他元素设为null(代码:onProcess: buffers[buffers.length-3]=null),不保留也行,全部设为null,以释放占用的内存;rec.buffers将会自动清理,无需手动清理;注意:提供set.takeoffEncodeChunk时,应当延迟一下清理,不然buffers被清理掉时,这个buffers还未推入编码器进行编码;
  3. wav等不支持实时编码的格式录音结束时可以不用调用stop,直接调用close丢弃所有数据即可,因为只要buffers[0]==null时wav格式调用stop永远会直接走fail回调;mp3、pcm等支持实时编码的格式无此限制,且需要调用stop来编码输出最后一段音频数据。

【属性】rec.dataType

stop时返回的录音数据类型,取值:blob、arraybuffer,默认为blob(返回Blob文件对象);非浏览器环境中一般需要设为arraybuffer(返回ArrayBuffer对象),或直接设置全局属性Recorder.DefaultDataType。

【属性】rec.srcSampleRate

浏览器提供的原始采样率,只有start或mock调用后才会有值,此采样率就是rec.buffers数据的采样率。

【方法】rec.envIn(pcmData,pcmAbsSum)

本方法是一个内部使用的最为核心方法,如果你不知道用途,请勿随意调用,配套的有私有方法envStart(mockEnvInfo,sampleRate)(私有方法请自行阅读源码),这两方法控制着录音的开启、实时音频输入逻辑,起到隔离平台环境差异的作用(Recorder、RecordApp共享使用了本机制,实现了录音过程和平台环境无关)。

通过调用本方法,会在当前正在录制的录音中追加进新的pcm数据,每次调用本方法都会触发onProcess回调,也会触发takeoffEncodeChunk回调;从而可以做到:实时转码pcm成其他格式、在录音过程中插入音频数据、在新的录音中注入之前老的录音的buffers数据可以做到接续录音 等业务逻辑,可参考上面的Demo片段列表中的新录音从老录音接续、或录制中途插入音频例子;含有envStart的完整调用例子可以参考这里面的testEnvIn方法。

pcmData:[Int16,...] 为一维数组,pcm音频数据的采样率必须是rec.srcSampleRate (如果不是,请用Recorder.SampleData()方法先转换好)

pcmAbsSum:pcmData所有采样的绝对值的和,用来传递给Recorder.PowerLevel方法计算音量百分比,最终是给onProcess使用,如果不需要计算音量百分比,直接给0即可。

【方法】rec.mock(pcmData,pcmSampleRate)

模拟一段录音数据,后面直接调用stop进行编码得到音频文件。需提供pcm数据 pcmData = [Int16,...] 为Int16Array一维数组,和pcm数据的采样率 pcmSampleRate。调用本方法后无需调用也无法调用open、close、start等方法,只能调用stop,如果之前已经开始了录音,前面的录音数据全部会被丢弃(一般不要共用一个rec,直接创建个新的再调用mock);本方法主要用于音频转码。

提示:在录音实时回调中配合Recorder.SampleData()方法使用效果更佳,可实时生成小片段语音文件。

注意:pcmData为一维数组,如果提供二维数组将会产生不可预料的错误;如果需要使用类似onProcess回调的buffers或者rec.buffers这种pcm列表(二维数组)时,可自行展开成一维,或者使用Recorder.SampleData()方法转换成一维。

javascript
//提供一个变量,用于连续转换buffers数据,注意开始新的转换时需要重置为null
var prevChunk=null;

//将onProcess回调中的buffers转成pcm   onProcess(buffers,powerLevel,duration,sampleRate)
//var chunk=Recorder.SampleData(buffers,sampleRate,sampleRate,prevChunk);

//将rec.buffers转成pcm
var chunk=Recorder.SampleData(rec.buffers,rec.srcSampleRate,rec.srcSampleRate,prevChunk);

prevChunk=chunk; //存起来,下次从当前已转换位置继续转换

//这就是上面的代码自动截取到的最新未转换pcm数据
//你也可以自行从buffers中截取出需要转码的pcm
//或者从其他地方获取到pcm数据,比如用解码其他音频文件得到pcm,即可实现不同音频格式之间转码
var pcm=chunk.data; //pcm=new Int16Array(pcmArrayBuffer) 可以使用16位pcm二进制数据直接构造
var sampleRate=chunk.sampleRate;

//调用mock方法把pcm转码成mp3或其他格式
var mockRec=Recorder({ type:"mp3",bitRate:16,sampleRate:16000 });
mockRec.mock(pcm,sampleRate);
mockRec.stop(function(blob,duration){
    console.log("pcm已转码成mp3",blob,duration);
},function(msg){
    console.error("不应该出现的错误:"+msg);
});

【静态方法】Recorder.Support()

判断浏览器是否支持录音,随时可以调用。注意:仅仅是检测浏览器支持情况,不会判断和调起用户授权(rec.open()会判断用户授权),不会判断是否支持特定格式录音。

【静态方法】Recorder.GetContext(tryNew)

获取AudioContext对象,如果浏览器不支持将返回null。tryNew=false时返回全局的Recorder.Ctx,Ctx.state可能是closed,仅限用于解码等操作。tryNew=true时会尝试创建新的ctx(不支持close的老浏览器依旧返回全局的),注意用完必须自己调用CloseNewCtx(ctx)关闭;注意:非用户操作(触摸、点击等)时调用返回的ctx.state可能是suspended状态,需要在用户操作时调用ctx.resume恢复成running状态,参考rec的runningContext配置。

本方法调用一次后,可通过Recorder.Ctx来获得全局对象,但仅可用于音频文件解码:Recorder.Ctx.decodeAudioData(fileArrayBuffer)。本方法是从老版本的Recorder.Support()中剥离出来的,调用Support会自动调用一次本方法。已知iOS16中全局对象无法多次用于录音,当前Recorder打开录音时均会尝试创建新的非全局对象,同时会保留一个全局的对象。

【静态方法】Recorder.IsOpen()

由于Recorder持有的普通麦克风录音资源是全局唯一的,可通过此方法检测是否有Recorder已调用过open打开了麦克风录音功能;注意:此方法无法检测直接提供的流 set.sourceStream 是否已打开,需自行判断。

【静态方法】Recorder.Destroy()

销毁已持有的所有全局资源(AudioContext、Worker),当要彻底移除Recorder时需要显式的调用此方法。大部分情况下不调用Destroy也不会造成问题。

【静态方法】Recorder.CLog

全局的日志输出函数,可赋值一个空函数来屏蔽Recorder的日志输出Recorder.CLog=function(){}。

【静态属性】Recorder.TrafficImgUrl

流量统计用1像素图片地址,在Recorder首次被实例化时将往这个地址发送一个请求,请求是通过Image对象来发送,安全可靠;默认开启统计,url为本库的51la统计用图片地址,为空响应流量消耗非常小,因此对使用几乎没有影响。

设置为空字符串后将不参与统计,大部分情况下无需关闭统计,如果你网页的url私密性要求很高,请在调用Recorder之前将此url设为空字符串;本功能于2019-11-09添加,点此前往51la查看统计概况。

【静态属性】Recorder.i18n

内置的简版国际化多语言支持实现,详细请参考下面的i18n章节。

【静态属性】Recorder.IsBrowser

当前环境是否是浏览器环境,260708版本新增。

【静态属性】Recorder.DefaultDataType

全局默认stop时返回的录音数据类型,取值:blob、arraybuffer,默认为blob(返回Blob文件对象),非浏览器环境中一般需要设为arraybuffer(返回ArrayBuffer对象);rec实例的dataType属性配置优先级更高。

【静态属性】Recorder.BufferSize

普通的麦克风录音时全局的AudioContext缓冲大小,默认值为4096。会影响H5录音时的onProcess调用速率,相对于AudioContext.sampleRate=48000时,4096接近12帧/s(移动端帧率可能会低一些),调节此参数可生成比较流畅的回调动画。

取值256, 512, 1024, 2048, 4096, 8192, or 16384

注意:取值不能过低,2048开始不同浏览器可能回调速率跟不上造成音质问题。一般无需调整,调整后需要先close掉已打开的录音,再open时才会生效。

如果是直接提供的流 set.sourceStream,不是默认的从麦克风录音时,这个属性可以改成由Recorder的实例提供,比如rec.BufferSize=1024,这样就不会受全局干扰。

这个属性在旧版Recorder中是放在已废弃的set.bufferSize中,后面因为兼容处理Safari上MediaStream断开后就无法再次进行连接使用的问题(表现为静音),把MediaStream连接也改成了全局只连接一次,因此set.bufferSize就移出来变成了Recorder的属性

【静态属性】Recorder.ConnectEnableWebM

音频采集连接方式:启用时尝试使用MediaRecorder.WebM.PCM,默认为true启用,未启用或者不支持时使用AudioWorklet或过时的ScriptProcessor来连接;本连接方式仅对普通麦克风录音时有效,直接提供了流(set.sourceStream)时将当做未启用处理。

使用MediaRecorder采集到的音频数据比其他方式更好,几乎不存在丢帧现象,所以音质明显会好很多,建议保持开启; 有些浏览器不支持录制PCM编码的WebM,如FireFox、低版本的Chrome,将依旧使用AudioWorklet或ScriptProcessor来连接采集。

可以额外提供一个设置Recorder.ConnectWebMOptions={}来当做MediaRecorder的options参数,支持的参数请参考此文档。

本连接实现原理:通过MediaRecorder对MediaStream进行录制,格式audio/webm; codecs=pcm,MediaRecorder会将实时录制的PCM数据(48k+32位)回传给js,因此只需要知道WebM的封装格式就能提取出PCM数据,请参考测试代码:WebM格式解析并提取音频

【静态属性】Recorder.ConnectEnableWorklet

音频采集连接方式:是否要启用AudioWorklet (AudioWorkletNode) 来进行连接;默认为false禁用,禁用后将使用过时的ScriptProcessor (AudioContext.createScriptProcessor) 来连接;如果启用了 Recorder.ConnectEnableWebM 并且有效时,本参数将不起作用,否则才会生效。

启用后如果浏览器不支持AudioWorklet,将只会使用老的ScriptProcessor来进行音频采集连接;如果浏览器已停止支持ScriptProcessor,将永远会尝试启用AudioWorklet而忽略此配置值。

未雨绸缪,目前只需要ScriptProcessor就能做到100%兼容所有浏览器;以后就算只能用AudioWorklet时,也还是需要保留ScriptProcessor用来支持老浏览器;所以默认为禁用,现在实现AudioWorklet的目的是让代码更经得起考验。

导致浏览器崩溃:某些浏览器的AudioWorklet和AudioContext的resume一起作用时会产生崩溃现象,错误代码:STATUS_ACCESS_VIOLATION;此坑已填好,复现测试页面。

注意:由于AudioWorklet内部1秒会产生375次回调,在移动端可能会有性能问题导致浏览器回调丢失,进而导致录音数据、时长变短,PC端似乎无此影响,可通过定时1分钟录音来检测影响(如果短了1秒以上即为有问题);在无明显优势好处的前提下,暂不建议启用。

【静态方法】Recorder.SampleData(pcmDatas,pcmSampleRate,newSampleRate,prevChunkInfo,option)

对pcm数据的采样率进行转换,支持流式转换,可配合mock方法可转换成音频文件,比如实时转换成小片段语音文件。

注意: 从241020版本开始,支持任意采样率转换;之前的老版本只会将高采样率的pcm转成低采样率的pcm,老版本由低转高时不会进行转换处理。

注意: 260708之前的版本,流式转换存在缺陷会增加噪音(单次完整转换正常),此版本已修复,流式转换结果和单次完整转换结果一致

pcmDatas: [[Int16,...]] pcm片段列表,二维数组,比如可以是:rec.buffers、onProcess中的buffers;二维数组里面是Int16Array,也可传Float32Array(会转成Int16Array)

pcmSampleRate:48000 pcm数据的采样率,比如用:rec.srcSampleRate、onProcess中的bufferSampleRate

newSampleRate:16000 需要转换成的采样率,241020版本后支持转成任意采样率,之前老版本仅支持转成低采样率<=pcmSampleRate

prevChunkInfo:{} 可选,上次调用时的返回值,用于连续的流式转换,本次调用将从上次结束位置开始进行处理。或可自行定义一个ChunkInfo从pcmDatas指定的位置开始进行转换

option:

javascript
option:{ 可选,配置项
        frameSize:123456 帧大小,每帧的PCM Int16的数量,采样率转换后的pcm长度为frameSize的整数倍,用于连续转换。目前仅在mp3格式时才有用,frameSize取值为1152,这样编码出来的mp3时长和pcm的时长完全一致,否则会因为mp3最后一帧录音不够填满时添加填充数据导致mp3的时长变长。
        frameType:"" 帧类型,一般为rec.set.type,提供此参数时无需提供frameSize,会自动使用最佳的值给frameSize赋值,目前仅支持mp3=1152(MPEG1 Layer3的每帧采采样数),其他类型=1。
            以上两个参数用于连续转换时使用,最多使用一个,不提供时不进行帧的特殊处理,提供时必须同时提供prevChunkInfo才有作用。最后一段数据处理时无需提供帧大小以便输出最后一丁点残留数据。
    }

返回值ChunkInfo

javascript
{
    //可定义,从指定位置开始转换到结尾
    index:0 pcmDatas已处理到的索引;比如每次都是单个pcm需要连续处理时,可每次调用前重置成0,pcmDatas仅需传入`[pcm]`固定一个元素
    offset:0.0 已处理到的index对应的pcm中的偏移的下一个位置(提升采样率时为结果的pcm)
    raisePrev:null 提升采样率时的前一个pcm结果采样值
    downPrev:null 降低采样率时的前一个pcm未添加的一个元素 null|{v:123,at:0.1}
    resizeCount:0 性能计数,内部调整输出缓冲区的次数,>0调整次数 0未发生调整 -1缓冲区发生溢出(-1需重设为0才会继续计数)
    
    //可定义,指定的一个滤波配置:默认使用Recorder.IIRFilter低通滤波(可有效抑制混叠产生的杂音,小采样率大于高采样率的75%时不默认滤波),如果提供了配置但fn为null时将不滤波;sr、srn为此滤波函数对应的初始化采样率,当采样率和参数的不一致时将重新设为默认函数
    filter:null||{fn:fn(sample),sr:pcmSampleRate,srn:newSampleRate}
    
    //仅作为返回值
    frameNext:null||[Int16,...] 下一帧的部分数据,frameSize设置了的时候才可能会有
    sampleRate:16000 结果的采样率=newSampleRate,241020版本前的老版本<=newSampleRate
    data:[Int16,...] 转换后的PCM结果(16位小端LE),为一维数组,可直接new Blob([data],{type:"audio/pcm"})生成Blob文件,或者使用mock方法转换成其他音频格式;注意:如果是连续转换,并且pcmDatas中并没有新数据时,data的长度可能为0
}

【静态方法】Recorder.IIRFilter(useLowPass,sampleRate,freq)

IIR低通、高通滤波;可重新赋值一个函数,来改变Recorder的默认行为,比如SampleData中的低通滤波。返回的是一个函数,用此函数对pcm的每个采样值按顺序进行处理即可(不同pcm不可共用);注意此函数返回值可能会越界超过Int16范围,自行限制一下即可:Math.min(Math.max(val,-0x8000),0x7FFF)。返回的是null,代表不支持此条件下进行滤波。默认实现为一阶IIR,滤波能力一般,但速度快,比不滤波音质强很多。

useLowPass: true或false,true为低通滤波,false为高通滤波

sampleRate: 待处理pcm的采样率

freq: 截止频率Hz,最大频率为sampleRate/2,低通时会切掉高于此频率的声音,高通时会切掉低于此频率的声音,注意滤波并非100%的切掉不需要的声音,而是减弱频率对应的声音,离截止频率越远对应声音减弱越厉害,离截止频率越近声音就几乎无衰减

【静态方法】Recorder.PowerLevel(pcmAbsSum,pcmLength)

计算音量百分比的一个方法,返回值:0-100,主要当做百分比用;注意:这个不是分贝,因此没用volume当做名称。

pcmAbsSum: pcm Int16所有采样的绝对值的和

pcmLength: pcm长度

【静态方法】Recorder.PowerDBFS(maxSample,roundOff)

计算音量,单位dBFS(满刻度相对电平),返回值:-100~0 (最大值0dB,最小值-100代替-∞),采样值0x7FFF=0dB 2=-84.2dB 1=-90.3dB 0为-Infinity取-100dB。

maxSample: 为16位pcm采样的绝对值中最大的一个(计算峰值音量),或者为pcm中所有采样的绝对值的平局值

roundOff: 默认false结果会四舍五入成整数,传true会返回小数部分,260708版本新增

​

​

​

​

​

​

:open_book:Extensions - 插件文档

在src/extensions目录内为插件支持库,这些插件库默认都没有合并到生成代码中,需单独引用(dist或src中的)才能使用。

【可移植】大部分可视化插件均可以移植到其他语言环境,比如:Android、iOS原生实现,如需定制可联系作者。

【附】部分插件使用效果图(在线运行观看):

​

​

WaveView插件

waveview.js,7kb大小源码,录音时动态显示波形,具体样子参考演示地址页面。此插件参考MCVoiceWave库编写的,具体代码在https://github.com/HaloMartin/MCVoiceWave/blob/f6dc28975fbe0f7fc6cc4dbc2e61b0aa5574e9bc/MCVoiceWave/MCVoiceWaveView.m中;本可视化插件可以移植到其他语言环境,如需定制可联系作者。

此插件是在录音时onProcess回调中使用。基础使用方法:

javascript
var wave;
var rec=Recorder({
    onProcess:function(buffers,powerLevel,bufferDuration,bufferSampleRate){
        wave.input(buffers[buffers.length-1],powerLevel,bufferSampleRate);//输入音频数据,更新显示波形
    }
});
rec.open(function(){
    wave=Recorder.WaveView({elem:".elem"}); //创建wave对象,写这里面浏览器妥妥的
    
    rec.start();
});

【构造】wave=Recorder.WaveView(set)

构造函数,set参数为配置对象,默认配置值如下:

javascript
set={
    elem:"css selector" //自动显示到dom,并以此dom大小为显示大小
        //或者配置显示大小,手动把waveviewObj.elem显示到别的地方
    ,width:0 //显示宽度
    ,height:0 //显示高度
//H5环境以上配置二选一

    compatibleCanvas: CanvasObject //提供一个兼容H5的canvas对象,需支持getContext("2d"),支持设置width、height,支持drawImage(canvas,...)
    ,width:0 //canvas显示宽度
    ,height:0 //canvas显示高度
//非H5环境使用以上配置,比如微信小程序、uni-app
    
    ,scale:2 //缩放系数,应为正整数,使用2(3? no!)倍宽高进行绘制,避免移动端绘制模糊
    ,speed:9 //移动速度系数,越大越快
    ,phase:21.8 //相位,调整了速度后,调整这个值得到一个看起来舒服的波形
    
    ,fps:20 //绘制帧率,调整后也需调整phase值
    ,keep:true //当停止了input输入时,是否保持波形,设为false停止后将变成一条线
    
    ,lineWidth:3 //线条基础粗细
            
    //渐变色配置:[位置,css颜色,...] 位置: 取值0.0-1.0之间
    ,linear1:[0,"rgba(150,96,238,1)",0.2,"rgba(170,79,249,1)",1,"rgba(53,199,253,1)"] //线条渐变色1,从左到右
    ,linear2:[0,"rgba(209,130,255,0.6)",1,"rgba(53,199,255,0.6)"] //线条渐变色2,从左到右
    ,linearBg:[0,"rgba(255,255,255,0.2)",1,"rgba(54,197,252,0.2)"] //背景渐变色,从上到下
}

【方法】wave.input(pcmData,powerLevel,sampleRate)

输入音频数据,更新波形显示。pcmData [Int16,...] 一维数组,为当前的录音数据片段,其他参数和onProcess回调相同。

​

​

WaveSurferView插件

wavesurfer.view.js,8kb大小源码,音频可视化波形显示,具体样子参考演示地址页面;本可视化插件可以移植到其他语言环境,如需定制可联系作者。

此插件的使用方式和WaveView插件完全相同,请参考上面的WaveView来使用;本插件的波形绘制直接简单的使用PCM的采样数值大小来进行线条的绘制,同一段音频绘制出的波形和Audition内显示的波形外观上几乎没有差异。

已知问题:iOS上微信小程序基础库存在bug,canvas.drawImage(canvas)可能无法绘制,可能会导致本可视化插件在iOS小程序上不能正确显示,其他环境下无此兼容性问题。

【构造】surfer=Recorder.WaveSurferView(set)

构造函数,set参数为配置对象,默认配置值如下:

javascript
set={
    elem:"css selector" //自动显示到dom,并以此dom大小为显示大小
        //或者配置显示大小,手动把surferObj.elem显示到别的地方
    ,width:0 //显示宽度
    ,height:0 //显示高度
//H5环境以上配置二选一

    compatibleCanvas: CanvasObject //提供一个兼容H5的canvas对象,需支持getContext("2d"),支持设置width、height,支持drawImage(canvas,...)
    ,compatibleCanvas_2x: CanvasObject //提供一个宽度是compatibleCanvas的2倍canvas对象
    ,width:0 //canvas显示宽度
    ,height:0 //canvas显示高度
//非H5环境使用以上配置,比如微信小程序、uni-app
    
    ,scale:2 //缩放系数,应为正整数,使用2(3? no!)倍宽高进行绘制,避免移动端绘制模糊
    
    ,fps:50 //绘制帧率,不可过高,50-60fps运动性质动画明显会流畅舒适,实际显示帧率达不到这个值也并无太大影响
    
    ,duration:2500 //当前视图窗口内最大绘制的波形的持续时间,此处决定了移动速率
    ,direction:1 //波形前进方向,取值:1由左往右,-1由右往左
    ,position:0 //绘制位置,取值-1到1,-1为最底下,0为中间,1为最顶上,小数为百分比
    
    ,centerHeight:1 //中线基础粗细,如果为0不绘制中线,position=±1时应当设为0
    
    //波形颜色配置:[位置,css颜色,...] 位置: 取值0.0-1.0之间
    ,linear:[0,"rgba(0,187,17,1)",0.7,"rgba(255,215,0,1)",1,"rgba(255,102,0,1)"]
    ,centerColor:"" //中线css颜色,留空取波形第一个渐变颜色
}

【方法】surfer.input(pcmData,powerLevel,sampleRate)

输入音频数据,更新波形显示。pcmData [Int16,...] 一维数组,为当前的录音数据片段,其他参数和onProcess回调相同。

​

​

FrequencyHistogramView插件

frequency.histogram.view.js + lib.fft.js,12kb大小源码,音频可视化频率直方图显示,具体样子参考演示地址页面。此插件核心算法参考Java开源库jmp123的代码编写的,jmp123版本0.3;直方图特意优化主要显示0-5khz语音部分(线性),其他高频显示区域较小,不适合用来展示音乐频谱,可通过配置fullFreq来恢复成完整的线性频谱,或自行修改源码修改成倍频程频谱(伯德图、对数频谱);本可视化插件可以移植到其他语言环境,如需定制可联系作者。

此插件的使用方式和WaveView插件完全相同,请参考上面的WaveView来使用;请注意:必须同时引入lib.fft.js才能正常工作。

【构造】histogram=Recorder.FrequencyHistogramView(set)

构造函数,set参数为配置对象,默认配置值如下:

javascript
set={
    elem:"css selector" //自动显示到dom,并以此dom大小为显示大小
        //或者配置显示大小,手动把frequencyObj.elem显示到别的地方
    ,width:0 //显示宽度
    ,height:0 //显示高度
//H5环境以上配置二选一

    compatibleCanvas: CanvasObject //提供一个兼容H5的canvas对象,需支持getContext("2d"),支持设置width、height,支持drawImage(canvas,...)
    ,width:0 //canvas显示宽度
    ,height:0 //canvas显示高度
//非H5环境使用以上配置,比如微信小程序、uni-app
    
    ,scale:2 //缩放系数,应为正整数,使用2(3? no!)倍宽高进行绘制,避免移动端绘制模糊
    
    ,fps:20 //绘制帧率,不可过高
    
    ,lineCount:30 //直方图柱子数量,数量的多少对性能影响不大,密集运算集中在FFT算法中
    ,widthRatio:0.6 //柱子线条宽度占比,为所有柱子占用整个视图宽度的比例,剩下的空白区域均匀插入柱子中间;默认值也基本相当于一根柱子占0.6,一根空白占0.4;设为1不留空白,当视图不足容下所有柱子时也不留空白
    ,spaceWidth:0 //柱子间空白固定基础宽度,柱子宽度自适应,当不为0时widthRatio无效,当视图不足容下所有柱子时将不会留空白,允许为负数,让柱子发生重叠
    ,minHeight:0 //柱子保留基础高度,position不为±1时应该保留点高度
    ,position:-1 //绘制位置,取值-1到1,-1为最底下,0为中间,1为最顶上,小数为百分比
    ,mirrorEnable:false //是否启用镜像,如果启用,视图宽度会分成左右两块,右边这块进行绘制,左边这块进行镜像(以中间这根柱子的中心进行镜像)
    
    ,stripeEnable:true //是否启用柱子顶上的峰值小横条,position不是-1时应当关闭,否则会很丑
    ,stripeHeight:3 //峰值小横条基础高度
    ,stripeMargin:6 //峰值小横条和柱子保持的基础距离
    
    ,fallDuration:1000 //柱子从最顶上下降到最底部最长时间ms
    ,stripeFallDuration:3500 //峰值小横条从最顶上下降到底部最长时间ms
    
    //柱子颜色配置:[位置,css颜色,...] 位置: 取值0.0-1.0之间
    ,linear:[0,"rgba(0,187,17,1)",0.5,"rgba(255,215,0,1)",1,"rgba(255,102,0,1)"]
    //峰值小横条渐变颜色配置,取值格式和linear一致,留空为柱子的渐变颜色
    ,stripeLinear:null
    
    ,shadowBlur:0 //柱子阴影基础大小,设为0不显示阴影,如果柱子数量太多时请勿开启,非常影响性能
    ,shadowColor:"#bbb" //柱子阴影颜色
    ,stripeShadowBlur:-1 //峰值小横条阴影基础大小,设为0不显示阴影,-1为柱子的大小,如果柱子数量太多时请勿开启,非常影响性能
    ,stripeShadowColor:"" //峰值小横条阴影颜色,留空为柱子的阴影颜色
    
    ,fullFreq:false //是否要绘制所有频率;默认false主要绘制5khz以下的频率,高频部分占比很少,此时不同的采样率对频谱显示几乎没有影响;设为true后不同采样率下显示的频谱是不一样的,因为 最大频率=采样率/2 会有差异
    //当发生绘制时会回调此方法,参数为当前绘制的频率数据和采样率,可实现多个直方图同时绘制,只消耗一个input输入和计算时间
    ,onDraw:function(frequencyData,sampleRate){}
}

【方法】histogram.input(pcmData,powerLevel,sampleRate)

输入音频数据,更新直方图显示。pcmData [Int16,...] 一维数组,为当前的录音数据片段,其他参数和onProcess回调相同。

​

​

BufferStreamPlayer插件

buffer_stream.player.js,31kb大小源码,实时播放录音片段文件,把片段文件转换成MediaStream流,参考此demo片段在线测试使用。

BufferStreamPlayer可以通过input方法一次性输入整个音频文件,或者实时输入音频片段文件,然后播放出来;输入支持格式:pcm、wav、mp3等浏览器支持的音频格式,非pcm格式会自动解码成pcm(播放音质效果比pcm、wav格式差点);输入前输入后都可进行处理要播放的音频,比如:混音、变速、变调;输入的音频会写入到内部的MediaStream流中,完成将连续的音频片段文件转换成流。

此插件仅支持在浏览器环境中使用。

可以用于

  1. Recorder onProcess等实时处理中,将实时处理好的音频片段转直接换成MediaStream,此流可以作为WebRTC的local流发送到对方,或播放出来;
  2. 接收到的音频片段文件的实时播放,比如:WebSocket接收到的录音片段文件播放、WebRTC remote流(Recorder支持对这种流进行实时处理)实时处理后的播放;
  3. 单个音频文件的实时播放处理,比如:播放一段音频,并同时进行可视化绘制(其实自己解码+播放绘制比直接调用这个更有趣,但这个省事、配套功能多点)。

BufferStreamPlayer文档

javascript
//【构造初始化】
var stream=Recorder.BufferStreamPlayer({
    play:true //要播放声音,设为false不播放,只提供MediaStream
    ,realtime:true /*默认为true实时模式,设为false为非实时模式
        实时模式:设为 true 或 {maxDelay:300,discardAll:false}配置对象
            如果有新的input输入数据,但之前输入的数据还未播放完的时长不超过maxDelay时(缓冲播放延迟默认限制在300ms内),如果积压的数据量过大则积压的数据将会被直接丢弃,少量积压会和新数据一起加速播放,最终达到尽快播放新输入的数据的目的;这在网络不流畅卡顿时会发挥很大作用,可有效降低播放延迟;出现加速播放时声音听起来会比较怪异,可配置discardAll=true来关闭此特性,少量积压的数据也直接丢弃,不会加速播放;如果你的音频数据块超过200ms,需要调大maxDelay(取值100-800ms)
        非实时模式:设为 false
            连续完整的播放完所有input输入的数据,之前输入的还未播放完又有新input输入会加入队列排队播放,比如用于:一次性同时输入几段音频完整播放
        */
            
    //,onInputError:fn(errMsg, inputIndex) //当input输入出错时回调,参数为input第几次调用和错误消息
    //,onUpdateTime:fn() //已播放时长、总时长更新回调(stop、pause、resume后一定会回调),this.currentTime为已播放时长,this.duration为已输入的全部数据总时长(实时模式下意义不大,会比实际播放的长),单位都是ms
    //,onPlayEnd:fn() //没有可播放的数据时回调(stop后一定会回调),已输入的数据已全部播放完了,可代表正在缓冲中或播放结束;之后如果继续input输入了新数据,播放完后会再次回调,因此会多次回调;非实时模式一次性输入了数据时,此回调相当于播放完成,可以stop掉,重新创建对象来input数据可达到循环播放效果
    
    //,decode:false //input输入的数据在调用transform之前是否要进行一次音频解码成pcm [Int16,...]
        //mp3、wav等都可以设为true、或设为{fadeInOut:true}配置对象,会自动解码成pcm;默认会开启fadeInOut对解码的pcm首尾进行淡入淡出处理,减少爆音(wav等解码后和原始pcm一致的音频,可以把fadeInOut设为false)
    
    //transform:fn(inputData,sampleRate,True,False)
        //将input输入的data(如果开启了decode将是解码后的pcm)转换处理成要播放的pcm数据;如果没有解码也没有提供本方法,input的data必须是[Int16,...]并且设置set.sampleRate
        //inputData:any input方法输入的任意格式数据,只要这个转换函数支持处理;如果开启了decode,此数据为input输入的数据解码后的pcm [Int16,...]
        //sampleRate:123 如果设置了decode为解码后的采样率,否则为set.sampleRate || null
        //True(pcm,sampleRate) 回调处理好的pcm数据([Int16,...])和pcm的采样率
        //False(errMsg) 处理失败回调
        
    //sampleRate:16000 //可选input输入的数据默认的采样率,当没有设置解码也没有提供transform时应当明确设置采样率

    //runningContext:AudioContext //可选提供一个state为running状态的AudioContext对象(ctx),默认会在start时自动创建一个新的ctx,这个配置的作用请参阅Recorder的runningContext配置
});

//创建好后第一件事就是start打开流,打开后就会开始播放input输入的音频;注意:start需要在用户操作(触摸、点击等)时进行调用,原因参考runningContext配置
stream.start(()=>{
    stream.currentTime;//当前已播放的时长,单位ms,数值变化时会有onUpdateTime事件
    stream.duration;//已输入的全部数据总时长,单位ms,数值变化时会有onUpdateTime事件;实时模式下意义不大,会比实际播放的长,因为实时播放时卡了就会丢弃部分数据不播放
    stream.isStop;//是否已停止,调用了stop方法时会设为true
    stream.isPause;//是否已暂停,调用了pause方法时会设为true
    stream.isPlayEnd;//已输入的数据是否播放到了结尾(没有可播放的数据了),input后又会变成false;可代表正在缓冲中或播放结束,状态变更时会有onPlayEnd事件
    
    //如果不要默认的播放,可以设置set.play为false,这种情况下只拿到MediaStream来用
    stream.getMediaStream() //通过getMediaStream方法得到MediaStream流,此流可以作为WebRTC的local流发送到对方,或者直接拿来赋值给audio.srcObject来播放(和赋值audio.src作用一致);未start时调用此方法将会抛异常
    
    stream.getAudioSrc() //【已过时】超低版本浏览器中得到MediaStream流的字符串播放地址,可赋值给audio标签的src,直接播放音频;未start时调用此方法将会抛异常;新版本浏览器已停止支持将MediaStream转换成url字符串,调用本方法新浏览器会抛异常,因此在不需要兼容不支持srcObject的超低版本浏览器时,请直接使用getMediaStream然后赋值给auido.srcObject来播放
},(errMsg)=>{
    //start失败,无法播放
});

//随时都能调用input,会等到start成功后播放出来,不停的调用input,就能持续的播放出声音了,需要暂停播放就不要调用input就行了
stream.input(anyData);
//清除已输入但还未播放的数据,一般用于非实时模式打断老的播放;返回清除的音频时长,默认会从总时长duration中减去此时长,keepDuration=true时不减去
stream.clearInput(keepDuration);

//暂停播放,暂停后:实时模式下会丢弃所有input输入的数据(resume时只播放新input的数据),非实时模式下所有input输入的数据会保留到resume时继续播放
stream.pause();
//恢复播放,实时模式下只会从最新input的数据开始播放,非实时模式下会从暂停的位置继续播放
stream.resume();

//不要播放了就调用stop停止播放,关闭所有资源
stream.stop();

//实时设置播放音量:取值0.0-5.0(超过5也行,但削峰严重音质明显变差),0.0静音,1.0不改变音量,0.5降低一倍音量,2.0放大一倍音量;注意:此参数不是修改设备系统音量,是播放时pcm数据直接*volume
stream.volume=0.5;

【方法】stream.input(anyData)

输入任意格式的音频数据,未完成start前调用会等到start成功后生效。

text
anyData: any 具体类型取决于:
    set.decode为false时:
        未提供set.transform,数据必须是pcm[Int16,...],此时的set必须提供sampleRate;
        提供了set.transform,数据为transform方法支持的任意格式。
    set.decode为true时:
        数据必须是ArrayBuffer,会自动解码成pcm[Int16,...];注意输入的每一片数据都应该是完整的一个音频片段文件,否则可能会解码失败。
        
关于anyData的二进制长度:
    如果是提供的pcm、wav格式数据,数据长度对播放无太大影响,很短的数据也能很好的连续播放。
    如果是提供的mp3这种必须解码才能获得pcm的数据,数据应当尽量长点,测试发现片段有300ms以上解码后能很好的连续播放,低于100ms解码后可能会有明显的杂音,更低的可能会解码失败;当片段确实太小时,可以将本来会多次input调用的数据缓冲起来,等数据量达到了300ms再来调用一次input,能比较显著的改善播放音质。

​

​

ASR_Aliyun_Short插件

asr.aliyun.short.js,29kb大小源码,ASR,阿里云语音识别(语音转文字),支持实时语音识别、单个音频文件转文字,参考此demo片段在线测试使用。

  • 本插件通过调用 阿里云-智能语音交互-一句话识别 接口来进行语音识别,无时长限制。
  • 识别过程中采用WebSocket直连阿里云,语音数据无需经过自己服务器。
  • 自己服务器仅需提供一个Token生成接口即可(本库已实现一个本地测试NodeJs后端程序 /assets/demo-asr)。
  • 也支持在非浏览器环境中使用,比如微信小程序、uni-app,提供compatibleWebSocket进行适配即可。

本插件单次语音识别时虽长无限制,最佳使用场景还是1-5分钟内的语音识别;60分钟以上的语音识别本插件也能胜任(需自行进行重试容错处理),但太长的识别场景不太适合使用阿里云一句话识别(阿里云单次一句话识别最长60秒,本插件自带拼接过程,所以无时长限制);为什么采用一句话识别:因为便宜。

目前暂未提供其他版本的语音识别插件,比如腾讯云、讯飞等,搭配使用Recorder的onProcess实时处理,可根据自己的业务需求选择对应厂家自行对接即可,如需定制开发请联系作者。

对接流程

  1. 到阿里云开通 一句话识别 服务(可试用一段时间,正式使用时应当开通商用版,很便宜),得到AccessKey、Secret,参考:https://help.aliyun.com/document_detail/324194.html ;
  2. 到阿里云智能语音交互控制台创建相应的语音识别项目,并配置好项目,得到Appkey,每个项目可以设置一种语言模型,要支持多种语言就创建多个项目;
  3. 需要后端提供一个Token生成接口(用到上面的Key和Secret),可直接参考或本地运行此NodeJs后端测试程序:/assets/demo-asr,配置好代码里的阿里云账号后,在目录内直接命令行执行node NodeJsServer_asr.aliyun.short.js即可运行提供本地测试接口;
  4. 前端调用ASR_Aliyun_Short,传入tokenApi,即可很简单的实现语音识别功能。

ASR_Aliyun_Short文档

javascript
var rec=Recorder(recSet);rec.open(...) //进行语音识别前,先打开录音,获得录音权限

//【构造初始化】
var asr=Recorder.ASR_Aliyun_Short({
    tokenApi:"" /*必填,调用阿里云一句话识别需要的token获取api地址
            接口实现请参考本地测试NodeJs后端程序:/assets/demo-asr/NodeJsServer_asr.aliyun.short.js
            此接口默认需要返回数据格式:
                {
                    c:0 //code,0接口调用正常,其他数值接口调用出错
                    ,m:"" //message,接口调用出错时的错误消息
                    ,v:{ //value,接口成功调用返回的结果【结果中必须包含下面两个值】
                        appkey:"aaaa" //lang语言模型对应的项目appkey
                        ,token:"bbbb" //语音识别Access Token
                    }
                }
            如果不是返回的这个格式的数据,必须提供apiRequest配置,自行请求api*/
    ,apiArgs:{ //请求tokenApi时要传的参数
        action:"token"
        ,lang:"普通话" //语言模型设置(具体取值取决于tokenApi支持了哪些语言)
    }
    ,apiRequest:fn(url,args,success,fail) /*tokenApi的请求实现方法,默认使用简单的ajax实现
            如果你接口返回的数据格式和默认格式不一致,必须提供一个函数来自行请求api
            方法参数:
                url:"" == tokenApi
                args:{} == apiArgs
                success:fn(value) 接口调用成功回调,value={appkey:"", token:""}
                fail:fn(errMsg) 接口调用出错回调,errMsg="错误消息"
            */
    ,compatibleWebSocket:null /*提供一个函数返回兼容WebSocket的对象,一般也需要提供apiRequest
            如果你使用的环境不支持WebSocket(如微信小程序、uni-app),需要提供一个函数来返回一个兼容实现对象
            方法参数:fn(url) url为连接地址,返回一个对象,需支持的回调和方法:{
                    onopen:fn() 连接成功回调
                    onerror:fn({message}) 连接失败回调
                    onclose:fn({code, reason}) 连接关闭回调
                    onmessage:fn({data}) 收到消息回调
                    connect:fn() 进行连接
                    close:fn(code,reason) 关闭连接
                    send:fn(data) 发送数据,data为字符串或者arraybuffer
                }
            binaryType固定使用arraybuffer类型
            */
    
    ,asrProcess:fn(text,nextDuration,abortMsg) //当实时接收到语音识别结果时的回调函数(对单个完整音频文件的识别也有效)
            //此方法需要返回true才会继续识别,否则立即当做识别超时处理,你应当通过nextDuration来决定是否继续识别,避免无限制的识别大量消耗阿里云资源额度;如果不提供本回调,默认1分钟超时后终止识别(因为没有绑定回调,你不知道已经被终止了)
            //text为中间识别到的内容(并非已有录音片段的最终结果,后续可能会根据语境修整)
            //nextDuration 为当前回调时下次即将进行识别的总时长,单位毫秒,通过这个参数来限制识别总时长,超过时长就返回false终止识别(第二分钟开始每分钟会多识别前一分钟结尾的5秒数据,用于两分钟之间的拼接,相当于第二分钟最多识别55秒的新内容)
            //abortMsg如不为空代表识别中途因为某种原因终止了识别(比如超时、接口调用失败),收到此信息时应当立即调用asr的stop方法得到最终结果,并且终止录音
    
    ,log:fn(msg,color) //提供一个日志输出接口,默认只会输出到控制台,color: 1:红色,2绿色,不为空时为颜色字符串
    
    //高级选项
    ,fileSpeed:6 //单个文件识别发送速度控制,取值1-n;1:为按播放速率发送,最慢,识别精度完美;6:按六倍播放速度发送,花10秒识别60秒文件比较快,精度还行;再快测试发现似乎会缺失内容,可能是发送太快底层识别不过来导致返回的结果缺失。
});

/**asr创建好后,随时调用strat,开始进行语音识别,开始后需要调用input输入录音数据,结束时调用stop来停止识别。如果start之前调用了input输入数据,这些数据将会等到start成功之后进行识别。
建议在success回调中开始录音(即rec.start);当然asr.start和rec.start同时进行调用,或者任意一个先调用都是允许的,不过当出现fail时,需要处理好asr和rec各自的状态。
无需特殊处理start和stop的关系,只要调用了stop,会阻止未完成的start,不会执行回调。
    success:fn()
    fail:fn(errMsg)**/
asr.start(function(){//success
    rec.start();//一般在start成功之后,调用rec.start()开始录音,此时可以通知用户讲话了
},fail);

/**实时处理输入音频数据,一般是在rec.set.onProcess中调用本方法,输入实时录制的音频数据,输入的数据将会发送语音识别;不管有没有start,都可以调用本方法,start前输入的数据会缓冲起来等到start后进行识别
    buffers:[[Int16...],...] pcm片段列表,为二维数组,第一维数组内存放1个或多个pcm数据;比如可以是:rec.buffers、onProcess中的buffers截取的一段新二维数组
    sampleRate:48000 buffers中pcm的采样率
    
    buffersOffset:0 可选,默认0,从buffers第一维的这个位置开始识别,方便rec的onProcess中使用**/
asr.input(buffers,sampleRate,buffersOffset); 

/**话讲完后,调用stop结束语音识别,得到识别到的内容文本,一般在调用了本方法后,下一行代码立即调用录音rec.stop结束录音
    success:fn(text,abortMsg) text为识别到的最终完整内容;如果存在abortMsg代表识别中途被某种错误停止了,text是停止前的内容识别到的完整内容,一般早在asrProcess中会收到abort事件然后要停止录音
    fail:fn(errMsg)**/
asr.stop(function(text,abortMsg){//success
    //text为识别到的最终完整内容;如果存在abortMsg代表识别中途被某种错误停止了,text是停止前的内容识别到的完整内容,一般早在asrProcess中会收到abort事件然后要停止录音
},fail);


//【更多的方法】
asr.inputDuration() //获取input已输入的音频数据总时长,单位ms
asr.sendDuration() //获取已发送识别的音频数据总时长,存在重发重叠部分,因此比inputDuration长
asr.asrDuration() //获取已识别的音频数据总时长,去除了sendDuration的重叠部分,值<=inputDuration
asr.getText() //获取实时结果文本,如果已stop返回的就是最终文本,一般无需调用此方法,因为回调中都提供了此方法的返回值

/**一次性将单个完整音频文件转成文字,无需start、stop,创建好asr后直接调用本方法即可,支持的文件类型由具体的浏览器决定,因此存在兼容性问题,兼容性mp3最好,wav次之,其他格式不一定能够解码。实际就是调用:浏览器解码音频得到PCM -> start -> input ... input -> stop
    audioBlob 音频文件Blob对象,如:rec.stop得到的录音结果、file input选择的文件、XMLHttpRequest的blob结果、new Blob([TypedArray])创建的blob
    success fn(text,abortMsg) text为识别到的完整内容,abortMsg参考stop
    fail:fn(errMsg)**/
asr.audioToText(audioBlob,success,fail)

/**一次性的将单个完整音频转成文字,无需start、stop,创建好asr后直接调用本方法即可。实际就是调用:start -> input ... input -> stop
    buffer:[Int16,...] 16位单声道音频pcm数据,一维数组
    sampleRate pcm的采样率
    success fn(text,abortMsg) text为识别到的完整内容,abortMsg参考stop
    fail:fn(errMsg)**/
asr.pcmToText(buffer,sampleRate,success,fail)

​

​

Sonic插件

sonic.js,38kb大小源码,音频变速变调转换,参考此demo片段在线测试使用。此插件从Sonic.java移植,并做了适当精简。

可到assets/sonic-java目录运行java代码测试原版效果。

本插件支持

  1. Pitch:变调不变速(会说话的汤姆猫),男女变声,只调整音调,不改变播放速度
  2. Speed:变速不变调(快放慢放),只调整播放速度,不改变音调
  3. Rate:变速变调,会改变播放速度和音调
  4. Volume:支持调整音量
  5. 支持实时处理,可在onProcess中实时处理PCM(需开启异步),配合SampleData方法使用更佳

Sonic文档

Sonic有两个构造方法,一个是同步方法,Sonic.Async是异步方法,同步方法简单直接但处理量大时会消耗大量时间,主要用于一次性的处理;异步方法由WebWorker在后台进行运算处理,但异步方法不一定能成功开启(低版本浏览器),主要用于实时处理。异步方法调用后必须调用flush方法,否则会产生内存泄露。

注意:由于同步方法转换操作需要占用比较多的CPU(但比转码小点),因此实时处理时在低端设备上可能会导致性能问题;在一次性处理大量pcm时,可采取切片+setTimeout进行处理,参考上面的demo片段。

注意:变速变调会大幅增减PCM数据长度,如果需要在onProcess中实时处理PCM,需要在rec.set中设置内部参数rec.set.disableEnvInFix=true来禁用设备卡顿时音频输入丢失补偿功能,否则可能导致错误的识别为设备卡顿。

注意:每次input输入的数据量应该尽量的大些,太少容易产生杂音,每次传入200ms以上的数据量就几乎没有影响了。

javascript
//【构造初始化】
var sonic=Recorder.Sonic(set) //同步调用,用于一次性处理
var sonic=Recorder.Sonic.Async(set) //异步调用,用于实时处理,调用后必须调用flush方法,否则会产生内存泄露。
    /*set:{
        sampleRate:待处理pcm的采样率,就是input输入的buffer的采样率
    }*/

//【功能配置调用函数】同步异步通用,以下num取值正常为0.1-2.0,超过这个范围也是可以的,但不推荐
sonic.setPitch(num)  //num:0.1-n,变调不变速(会说话的汤姆猫),男女变声,只调整音调,不改变播放速度,默认为1.0不调整
sonic.setSpeed(num)  //num:0.1-n,变速不变调(快放慢放),只调整播放速度,不改变音调,默认为1.0不调整
sonic.setRate(num)  //num:0.1-n,变速变调,越小越缓重,越大越尖锐,会改变播放速度和音调,默认为1.0不调整
sonic.setVolume(num)  //num:0.1-n,调整音量,默认为1.0不调整
sonic.setChordPitch(bool)  //bool:默认false,作用未知,不推荐使用
sonic.setQuality(num)  //num:0或1,默认0时会减小输入采样率来提供处理速度,变调时才会用到,不推荐使用

//【同步调用方法】
sonic.input(buffer)  //buffer:[Int16,...] 一维数组,输入pcm数据,返回转换后的部分pcm数据,完整输出需要调用flush;返回值[Int16,...]长度可能为0,代表没有数据被转换;此方法是耗时的方法,一次性处理大量pcm需要切片+setTimeout优化
sonic.flush()  //将残余的未转换的pcm数据完成转换并返回;返回值[Int16,...]长度可能为0,代表没有数据被转换

//【异步调用方法】
sonic.input(buffer,callback) //callback:fn(pcm),和同步方法相同,只是返回值通过callback返回
sonic.flush(callback) //callback:fn(pcm),和同步方法相同,只是返回值通过callback返回

​

​

NMN2PCM插件

create-audio.nmn2pcm.js,14kb大小源码,简单用 正弦波、方波、锯齿波、三角波 函数生成一段音乐简谱的pcm数据,主要用于测试时提供音频数据;参考此demo片段在线测试使用。

NMN2PCM文档

javascript
var pcmData=Recorder.NMN2PCM(set);
    set配置:{
        texts:""|["",""] 简谱格式化文本,如果格式不符合要求,将会抛异常
        sampleRate: 生成pcm的采样率,默认48000;取值不能过低,否则会削除高音
        timbre: 音色,默认2.0(使用音符对应频率的一个倍频),取值>=1.0
        meterDuration: 一拍时长,毫秒,默认600ms
        muteDuration: 音符之间的静默,毫秒,0时无静默,默认meterDur/4(最大50ms)
        beginDuration: 开头的静默时长,毫秒,0时无静默,默认为200ms
        endDuration: 结尾的静默时长,毫秒,0时无静默,默认为200ms
        
        volume: 音量,默认0.3,取值范围0.0-1.0(最大值1)
        waveType: 波形发生器类型,默认"sine",取值:sine(正弦波)、square(方波,volume应当减半)、sawtooth(锯齿波)、triangle(三角波)
    }
    其中texts格式比较复杂,请查看此插件源码中的注释
    
返回结果:{
        pcm: Int16Array,pcm数据
        duration: 123 pcm的时长,单位毫秒
        set: {...} 使用的set配置
        warns: [] 不适合抛异常的提示消息
    }

Recorder.NMN2PCM.GetExamples() 可获取内置的简谱

​

​

DTMF插件

dtmf.decode.js + lib.fft.js、dtmf.encode.js,两个js一个解码、一个编码,体积小均不超过10kb,纯js实现易于移植。参考此demo片段在线测试使用。

  1. DTMF(电话拨号按键信号)解码器,解码得到按键值:可实现实时从音频数据流中解码得到电话拨号按键信息,用于电话录音软解,软电话实时提取DTMF按键信号等;识别DTMF按键准确度高,误识别率低,支持识别120ms以上按键间隔+30ms以上的按键音,纯js实现易于移植;请注意:使用dtmf.decode.js必须同时引入lib.fft.js(由java移植过来的)才能正常工作。
  2. DTMF(电话拨号按键信号)编码生成器,生成按键对应的音频PCM信号:可实现生成按键对应的音频PCM信号,用于DTMF按键信号生成,软电话实时发送DTMF按键信号等;生成信号代码、原理简单粗暴,纯js实现易于移植,0依赖。

【方法】Recorder.DTMF_Decode(pcmData,sampleRate,prevChunk)

解码DTMF只有这个一个函数,此函数支持连续调用,将上次的返回值当做参数即可实现实时音频流数据的连续解码处理。

javascript
参数:
    pcmData:[Int16,...] pcm一维数组,原则上一次处理的数据量不要超过10秒,太长的数据应当分段延时处理
    sampleRate: 123 pcm的采样率
    prevChunk: null || {} 上次的返回值,用于连续识别,或者当做额外配置对象
    
返回:
    chunk:{
        keys:[keyItem,...] 识别到的按键,如果未识别到数组长度为0
                keyItem:{
                    key:"" //按键值 0-9 #*
                    time:123 //所在的时间位置,ms
                }
        
        //以下用于下次接续识别
        lastIs:"" "":mute {}:match 结尾处是什么
        lastCheckCount:0 结尾如果是key,此时的检查次数
        prevIs:"" "":null {}:match 上次疑似检测到了什么
        totalLen:0 总采样数,相对4khz
        pcm:[Int16,...] 4khz pcm数据
        
        //可额外配置值,如:DTMF_Decode(.., .., prevChunk||{checkFactor:2})
        checkFactor:3 信号检查因子,取值1,2,3,默认为3不支持低于32ms的按键音检测,当需要检测时可以设为2,当信号更恶劣时设为1,这样将会减少检查的次数,导致错误识别率变高
        debug:false 是否开启调试日志
    }

【方法】Recorder.DTMF_Encode(key,sampleRate,duration,mute)

本方法用来生成单个按键信号pcm数据,属于底层方法,要混合多个按键信号到别的pcm中请用封装好的DTMF_EncodeMix方法。

javascript
参数:
    key: 单个按键0-9#*
    sampleRate:123 要生成的pcm采样率
    duration:100 按键音持续时间
    mute:50 按键音前后静音时长
返回:
    pcm:[Int16,...],生成单个按键信号

【方法】Recorder.DTMF_EncodeMix(set)

本方法返回EncodeMix对象,将输入的按键信号混合到持续输入的pcm流中,当.mix(inputPcms)提供的太短的pcm会无法完整放下一个完整的按键信号,所以需要不停调用.mix(inputPcms)进行混合。

javascript
set={
    duration:100 //按键信号持续时间 ms,最小值为30ms
    ,mute:25 //按键音前后静音时长 ms,取值为0也是可以的
    ,interval:200 //两次按键信号间隔时长 ms,间隔内包含了duration+mute*2,最小值为120ms
}

EncodeMix对象:
    .add(keys)
        添加一个按键或多个按键 "0" "123#*",后面慢慢通过mix方法混合到pcm中,无返回值
    
    .mix(pcms,sampleRate,index)
        将已添加的按键信号混合到pcm中,pcms:[[Int16,...],...]二维数组,sampleRate:pcm的采样率,index:pcms第一维开始索引,将从这个pcm开始混合。
        返回状态对象:{
            newEncodes:[{key:"*",data:[Int16,...]},...] //本次混合新生成的按键信号列表 ,如果没有产生新信号将为空数组
            ,hasNext:false //是否还有未混合完的信号
        }
        注意:调用本方法会修改pcms中的内容,因此混合结果就在pcms内。

​

​

​

​

​

​

:open_book:已有的音频格式编码器

所有音频格式的编码器都在/src/engine目录中(或/dist/engine目录的压缩版),每个格式一般有一个同名的js文件,如果这个格式有额外的编码引擎文件(*-engine.js)的话,使用时必须要一起加上。

pcm 格式

依赖文件:pcm.js,支持实时编码(边录边转码),pcm编码器输出的数据其实就是Recorder中的buffers原始数据(经过了重新采样),16位时为LE小端模式(Little Endian),并未经过任何编码处理;pcm为未封装的原始音频数据,pcm数据文件无法直接播放,pcm加上一个44字节wav头即成wav文件,可通过wav格式来正常播放。两个参数相同的pcm文件直接二进制拼接在一起即可成为长的pcm文件。

Recorder.pcm2wav(data,True,False)

已实现的一个把pcm转成wav格式来播放的方法,data = { sampleRate:16000 pcm的采样率 , bitRate:16 pcm的位数 取值:8 或 16 , blob:pcm的blob对象 或 ArrayBuffer(回调也将返回ArrayBuffer) },True=fn(wavBlob,duration)。要使用此方法需要带上wav格式编码器。

提示:pcm二进制数据可以直接在数据开头添加个wav头(Recorder.wav_header)即成wav文件;16位的pcm可直接通过new Int16Array(pcmArrayBuffer)来构造成Int16Array,然后通过rec.mock来转码成其他任意格式。

wav (raw pcm format) 格式

依赖文件:wav.js(或使用根目录的recorder.wav.min.js一个文件即可),不支持实时编码(因为wav文件头中需要提供文件最终长度)只能一次性将所有pcm转成wav格式,wav格式编码器是参考网上资料写的,会发现代码和别人家的差不多。源码4kb大小。wav转其他格式参考和测试

生成的wav文件内音频数据的编码为未压缩的pcm数据(raw pcm),只是在pcm数据前面加了一个44字节的wav头;因此直接去掉前面44字节就能得到原始的pcm数据,如:blob.slice(44,blob.size,"audio/pcm");注意:其他wav编码器可能不是44字节的头,要从任意wav文件中提取pcm数据,可用下面的 wav_decode 来统一提取。

Recorder.wav_decode(wavBlob,True,False)

解码wav得到pcm,结果固定为16位单声道

js
wavBlob: wav音频文件blob对象(异步回调) 或 ArrayBuffer(同步回调),仅支持raw pcm格式的wav,单声道、双声道,8位、16位、24位、和32位浮点数
True(pcm,sampleRate,wavInfo) pcm为Int16Array,16位单声道,采样率为sampleRate
    wavInfo:{ //wav文件原始信息
        bitRate:8 16 24 32 //wav文件位数
        numChannels:1 2 //声道数
        dataPos:44 //数据开始位置,等于wav头长度
    }
False(msg)

Recorder.wav_header(format,numCh,sampleRate,bitRate,dataLength)

生成一个wav头(Uint8Array),添加到pcm二进制数据开头即成wav文件。比如实时保存pcm时(追加写入文件),可在文件开头预留44字节的位置,等录音结束后再来生成wav头并写入到文件开头。使用例子PCM基础教程。

format: 1 (raw pcm) 2 (ADPCM) 3 (IEEE Float) 6 (g711a) 7 (g711u),比如pcm传1,numCh: 声道数,比如1,sampleRate: 音频数据采样率,比如16000,bitRate: 音频数据位数,比如16,dataLength: wav中的音频数据二进制长度,比如pcm.byteLength。

简单将多段小的wav片段合成长的wav文件

由于RAW格式的wav内直接就是pcm数据,因此将小的wav片段文件去掉wav头后得到的原始pcm数据合并到一起,再加上新的wav头即可合并出长的wav文件;要求待合成的所有wav片段的采样率和位数需一致。wav合并参考和测试+可移植源码

mp3 (CBR) 格式

依赖文件:mp3.js + mp3-engine.js(或使用根目录的recorder.mp3.min.js一个文件即可),支持实时编码(边录边转码),采用的是lamejs(LGPL License)这个库的代码,https://github.com/zhuker/lamejs/blob/bfb7f6c6d7877e0fe1ad9e72697a871676119a0e/lame.all.js这个版本的文件代码;已对lamejs源码进行了部分改动,用于精简代码和修复发现的问题。LGPL协议涉及到的文件:mp3-engine.js;这些文件也采用LGPL授权,不适用MIT协议。源码366kb大小,压缩后130kb左右,开启gzip后60来k。mp3转其他格式参考和测试

注意: mp3编码器内部默认会自动进行低通滤波处理,来保证低比特率时把有限的比特数留给更重要的中低频,主动切掉一部分人耳不敏感的超高频信号;会导致16k采样率16kbps默认低通5550hz,声音沉闷但中低频厚实干净,禁用低通滤波后满频8k 音色丰富可提升清晰度 但会增加噪声 如金属音、水声。

低通滤波行为可通过set.engine_mp3_lowpassfreq来配置,默认0动态取低通截止频率,设为-1禁用,其他值为指定低通频率;另外可通过Recorder.Set_engine_mp3_lowpassfreq来进行全局配置(优先级比set内的低);为260708版本新增。

简单将多段小的mp3片段合成长的mp3文件

由于lamejs CBR编码出来的mp3二进制数据从头到尾全部是大小相同的数据帧(采样率44100等无法被8整除的部分帧可能存在额外多1字节填充),没有其他任何多余信息,通过文件长度可计算出mp3的时长fileSize*8/bitRate(参考),数据帧之间可以直接拼接。因此将小的mp3片段文件的二进制数据全部合并到一起即可得到长的mp3文件;要求待合成的所有mp3片段的采样率和比特率需一致。mp3合并参考和测试+可移植源码

注:CBR编码由于每帧数据的时长是固定的,mp3文件结尾最后这一帧的录音可能不能刚好填满,就会产生填充数据,多出来的这部分数据会导致mp3时长变长一点点,在实时转码传输时应当留意,解码成pcm后可直接去掉结尾的多余;另外可以通过调节待编码的pcm数据长度以达到刚好填满最后一帧来规避此问题,参考Recorder.SampleData方法提供的连续转码针对此问题的处理。首帧或前两帧可能是lame记录的信息帧,本库已去除(但小的mp3片段拼接起来停顿导致的杂音还是非常明显,实时处理时使用takeoffEncodeChunk选项可完全避免此问题),参考上面的已知问题。

g711a g711u 格式

依赖文件:g711x.js,g711a: G.711 A-law (pcma),g711u: G.711 μ-law (pcmu、mu-law);支持g711的编码和解码,支持实时编码(边录边转码),编解码源码移植自:https://github.com/twstx1/codec-for-audio-in-G72X-G711-G723-G726-G729/blob/master/G711_G721_G723/g711.c;固定为8000hz采样率、16位,每个采样压缩成8位存储,音频文件大小为8000字节/秒。

Recorder.g711a_encode|g711u_encode(pcm)

编码任意采样率的pcm得到g711x数据。pcm: Int16Array,任意采样率pcm数据(标准采样率为8000,其它采样率的pcm可使用Recorder.SampleData方法来转成8000采样率)。返回Uint8Array,g711x二进制数据(采样率为pcm的采样率)。

Recorder.g711a_decode|g711u_decode(bytes)

解码g711x得到pcm,bytes: Uint8Array,g711x二进制数据,采样率一般是8000;返回Int16Array,为g711x的采样率、16位的pcm数据。

Recorder.g711a2wav|g711u2wav(g711xBlob,True,False)

已实现把g711a、g711u转成wav格式来播放的方法,g711xBlob为g711x音频文件blob对象 或 ArrayBuffer(回调也将返回ArrayBuffer),采样率只支持8000,True=fn(wavBlob,duration);要使用此方法需要带上wav格式编码器。

ogg (Vorbis) 格式

依赖文件:beta-ogg.js + beta-ogg-engine.js,支持实时编码(边录边转码),采用的是ogg-vorbis-encoder-js(MIT License),https://github.com/higuma/ogg-vorbis-encoder-js/blob/7a872423f416e330e925f5266d2eb66cff63c1b6/lib/OggVorbisEncoder.js这个版本的文件代码。此编码器源码1.2M,比较大,开启gzip后400KB左右。对录音的压缩率比lamejs高出一倍, 但Vorbis in Ogg好像Safari不支持(真的假的)。

amr (NB 窄带) 格式

依赖文件:beta-amr.js + beta-amr-engine.js,支持实时编码(边录边转码),采用的是benz-amr-recorder(MIT License)优化后的amr.js(Unknown License),https://github.com/BenzLeung/benz-amr-recorder/blob/462c6b91a67f7d9f42d0579fb5906fad9edb2c9d/src/amrnb.js这个版本的文件代码,已对此代码进行过调整更方便使用。支持编码和解码操作。由于最高只有12.8kbps的码率(AMR 12.2,8000hz),音质和同比配置的mp3、ogg差一个档次。由于支持解码操作,理论上所有支持Audio的浏览器都可以播放(需要自己写代码实现)。此编码器源码600KB,比较大,开启gzip后130KB。优点:录音文件小。

Recorder.amr2wav(amrBlob,True,False)

已实现的一个把amr转成wav格式来播放的方法,True=fn(wavBlob,duration)。要使用此方法需要带上上面的wav格式编码器。仿照此方法可轻松转成别的格式,参考mock方法介绍那节。

amrBlob: amr音频文件blob对象 或 ArrayBuffer(回调也将返回ArrayBuffer)

AMR实时编码解码

使用Recorder.AMR.GetDecoder() | .GetEncoder(12.2)可以获得amr解码器和编码器,支持实时操作(amr帧、小文件片段连续编解码);如何使用请阅读beta-amr-engine.js源码开头部分代码,Recorder.amr2wav底层调用的Recorder.AMR.decode方法有用到。

beta-webm 格式

依赖文件:beta-webm.js,不支持实时编码,这个编码器时通过查阅MDN编写的一个玩意,没多大使用价值:录几秒就至少要几秒来编码。。。原因是:未找到对已有pcm数据进行快速编码的方法。数据导入到MediaRecorder,音频有几秒就要等几秒,类似边播放边收听形。(想接原始录音Stream?我不可能给的!)输出音频虽然可以通过比特率来控制文件大小,但音频文件中的比特率并非设定比特率,采样率由于是我们自己采样的,到这个编码器随他怎么搞。只有比较新的浏览器支持(需实现浏览器MediaRecorder),压缩率和mp3差不多。源码3kb大小。

【附】其他音频格式支持办法

javascript
//比如增加aac格式支持 (可参考/src/engine/wav.js的简单实现;如果要实现边录边转码应该参考mp3的实现,需实现的接口比较多)

//新增一个aac.js,编写以下格式代码即可实现这个类型
Recorder.prototype.aac=function(pcmData,successCall,failCall){
    //通过aac编码器把pcm[Int16,...]数据转成aac格式数据,通过this.set拿到传入的配置数据
    ... pcmData->aacData
    
    //返回数据
    successCall(new Blob([aacData.buffer],{type:"audio/aac"}));
}

//调用
Recorder({type:"aac"})

//如仅需要得到aac格式文件,你可以尝试直接用Recorder录制wav格式,然后调用 https://github.com/salomvary/fdk-aac.js库 来将wav转码成aac,详细请参考 assets/手动测试脚本.js 里面有可用的测试代码

​

​

​

:open_book:国际化多语言支持(i18n)

Recorder内置的简版国际化多语言支持实现,通过设置 Recorder.i18n.lang="en-US" 可切换Recorder内所有输出文本为英文(如:错误消息、控制台日志),默认为zh-CN简体中文。

语言文件在 /src/i18n 目录内,这些语言文件是通过/src/package-i18n.js脚本从src目录内所有源码文件中提取出文本自动生成的,如果修改了源码,需要重新运行一次脚本来生成语言文件,并对新增或修改的语句进行翻译;目前采用的是google翻译+百度翻译,由中文翻译成目标语言,翻译水平有限准确度一般。

  • en-US.js:English, 英文,lang="en-US" 或 lang="en"
  • es.js:Spanish, Español, 西班牙语,lang="es"
  • fr.js:French, Français, 法语,lang="fr"
  • Template.js:模板文件,当你需要的语言没有对应文件时,可以复制这个文件并改名,然后自行翻译成你需要的语言

切换语言

javascript
//比如切换成英文,在引入Recorder之后再进行语言文件的import
import 'recorder-core/src/i18n/en-US.js'
//<script src="src/i18n/en-US.js"></script>

Recorder.i18n.lang="en-US" //切换成对应的语言,此后Recorder的所有输出文本均为此语言的文本

i18n文档

javascript
var $T=Recorder.i18n.$T; //可以直接用变量引用$T多语言文本处理函数

//返回一个当前语言对应的文本,key必须唯一(可以用随机字符串);key如果不存在将会把文本存入当前语言实例中,存在时将返回已存在的文本;文本中可以用{1-n}提供参数;文本详细格式定义请阅读Recorder.i18n源码中的注释
$T("key-xx1::测试文本,{1},{2}",":Test Text, {1}, {2}",0,"args1","args2");

//可以直接在代码中提供多个语言文本;第一个默认zh-CN可以不写语言,第二个默认en-US可以不写语言,后面更多的必须写明是哪个语言
//Recorder.i18n.lang="ru" //设置当前语言为俄语
$T("key-xx2:zh-CN:俄语 {1}","en-US:Russian {1}","ru:Русский язык {1}",0,"args1");

//可以直接获取key对应的文本,第二个参数是文本中的参数数组,key如果不存在将返回"";$T.G这个函数主要用于一些预定义好的文本,免得多处出现相同的文本反复去翻译
$T.G("key-xx1",["v1","v2"]);
$T.G("key-xx2",["v1"], "ru"); //明确返回俄语,不管当前lang设置的是什么

​

​

​

:open_book:其它功能介绍

测试用的本地服务器

在 /assets/node-localServer 目录内提供了一个nodejs服务,实现了http上传接口、websocket接口。多个demo中有使用到,方便测试。

WebSocket语音通话聊天demo

在线测试Demo中包含了一个WebSocket版的语音通话聊天的测试功能,测试时需先运行上面这个本地服务器。用两个设备(浏览器打开两个标签也可以)打开demo,勾选实时语音通话聊天对讲,配置好双方标识,连接上服务器即可通话和聊天。手机上测试时,应当使用wss连接,本地服务器里面配置上ssl证书开启wss。

对应的源码在 /assets/zdemo.index.realtime_voice.js,实时传输pcm格式的音频数据(未经过任何编码),用BufferStreamPlayer插件播放。

WebRTC语音通话聊天demo

在线测试Demo中还包含了一个WebRTC版的语音通话聊天的测试功能,支持局域网内一对一语音无需服务器。用两个设备(浏览器打开两个标签也可以)打开demo,勾选实时语音通话聊天对讲再切换到WebRTC,按提示交换两个设备的信息即可成功进行P2P连接,然后进行语音。实际使用时数据传输用WebSocket的会简单好多。

早期编写本语音测试的目的在于验证H5录音实时转码、传输的可行性,并验证实时转码mp3格式小片段文件接收后的可播放性。经测试发现:除了移动端可能存在设备性能低下的问题以外,录音后实时转码mp3并传输给对方是可行的,对方接收后播放也能连贯的播放(效果还是要看播放代码写的怎么样,目前没有比较完美的播放代码,用BufferStreamPlayer插件播放效果会好点)。另外(16kbps,16khz)MP3开语音15分钟大概3M的流量,wav、pcm 15分钟要37M多流量。

另外除wav、pcm外MP3等格式编码出来的音频的播放时间比PCM原始数据要长一些或短一些,如果涉及到解码或拼接时,这个地方需要注意(如果类型支持,实时处理时使用takeoffEncodeChunk选项可完全避免此问题)。

​

​

​

:star:捐赠

如果这个库有帮助到您,请 Star 一下。

您也可以使用支付宝或微信打赏作者:


File: app-support-sample/README.md

【源GitHub仓库】 | 【Gitee镜像库】本文档中默认使用github.io部署的链接,如果无法访问,请直接下载仓库源码到本地然后双击打开html文件访问。

:open_book:RecordApp:基于Recorder的跨平台录音解决方案

RecordApp是在Recorder基础上为不同平台环境提供底层适配,支持在:浏览器WebView(H5)、各种使用js来构建的程序(App、小程序、UniApp、Electron、NodeJs)中录音。

使用场景 平台环境 参考Demo
App+H5页面 浏览器、WebView(App、软件内置浏览器) [ H5在线测试 ] [ QuickStart ] [ App vue ] [ Android App ] [ iOS App ]
微信小程序 微信小程序(支持可视化插件) 使用文档和Demo: [ miniProgram-wx ]
uni-app 可编译成:H5、Android iOS App、小程序 使用文档和Demo: [ demo_UniApp ]
NodeJs 无录音功能,可对PCM数据实时处理、编码 测试代码: [ node-codes ]
其他环境 可通过RecordApp.RegisterPlatform自行适配 自行对接

​

​

​

​

​

:open_book:快速使用

一、引入js文件

可以通过 npm 进行安装 npm install recorder-core --registry=https://registry.npmmirror.com/ ,或者直接git clone得到源码直接引用。所有js文件均为手动引入(内部不会自动引用),因此未被你引入的文件均可删除来精简源码大小。

​可以使用import、require、html script等你适合的方式来引入js文件,下面的以import为主要参考,其他引入方式根据文件路径自行调整一下就可以了。

javascript
//必须引入的Recorder核心(文件路径是 /src/recorder-core.js 下同),使用import、require都行;recorder-core会自动往window(浏览器环境)或Object(非浏览器环境)下挂载名称为Recorder对象,全局可调用Recorder
import Recorder from 'recorder-core' //注意如果未引用Recorder变量,可能编译时会被优化删除(如vue3 tree-shaking),请改成 import 'recorder-core',或随便调用一下 Recorder.a=1 保证强引用
//import './你clone的目录/src/recorder-core.js' //clone源码可以按这个方式引入,下同
//require('./你clone的目录/src/recorder-core.js') //clone源码可以按这个方式引入,下同
//<script src="你clone的目录/src/recorder-core.js"> //这是html中script方式引入,下同
    //import Recorder from 'recorder-core/src/recorder-core.js.esm.js' //ES Module内请用此方式

//按需引入你需要的录音格式支持文件,如果需要多个格式支持,把这些格式的编码引擎js文件统统引入进来即可
import 'recorder-core/src/engine/mp3'
import 'recorder-core/src/engine/mp3-engine' //如果此格式有额外的编码引擎(*-engine.js)的话,必须要加上

//以上三个也可以合并使用压缩好的recorder.xxx.min.js
//比如 import 'recorder-core/recorder.mp3.min' //已包含recorder-core和mp3格式支持
//比如 <script src="你clone的目录/recorder.mp3.min.js">

//可选的插件支持项,把需要的插件按需引入进来即可
import 'recorder-core/src/extensions/waveview'

/****以上均为Recorder的相关文件,下面是RecordApp需要的支持文件****/

//必须引入的RecordApp核心文件(文件路径是 /src/app-support/app.js)。注意:app.js会自动往window(浏览器环境)或Object(非浏览器环境)下挂载名称为RecordApp对象,全局可调用RecordApp
import RecordApp from 'recorder-core/src/app-support/app'
    //import RecordApp from 'recorder-core/src/app-support/app.js.esm.js' //ES Module内请用此方式

//引入特定平台环境下的支持文件(也可以统统引入进来,非对应的环境下运行时会忽略掉)
//import 'recorder-core/src/app-support/app-native-support.js' //App下的原生录音支持文件(App中未提供原生支持时可以不提供,统统走H5录音)
//import 'recorder-core/src/app-support/app-miniProgram-wx-support.js' //微信小程序下的录音支持文件
//import '@/uni_modules/Recorder-UniCore/app-uni-support.js' //uni-app下的支持文件,请参考本文档目录下的demo_UniApp测试项目

//ts import 提示:npm包内已自带了.d.ts声明文件(不过是any类型)

​

在App WebView、H5页面中使用

可以通过阅读和运行本目录下的QuickStart.html文件来快速入门学习,你可以直接将此html文件copy 到你的(https)网站中,无需其他文件,就能正常开始测试了,H5、Android App、iOS App内均适用。

RecordApp默认未开启App内原生录音支持,App内默认也使用H5录音(简单通用),可选开启支持后在App环境中将走Native原生录音(需要App配合)。

如何开启可选的原生录音支持:在Android、iOS App内使用demo_android、demo_ios目录内的RecordAppJsBridge.java或RecordAppJsBridge.swift 即可开启支持,本目录下的native-config.js文件内实现了对App的JsBridge调用(copy过去使用)。

关于App的系统录音权限:使用原生录音时,JsBridge会自动处理App的录音权限;使用H5录音时,需要先自行处理好App的系统录音权限;更多请参考下面的App权限部分。

javascript
//可选 开启App原生录音需要引入的文件,不引入时App中也统统使用默认的H5录音
import 'recorder-core/src/app-support/app-native-support.js' //App下的原生录音支持文件

//引入配置文件
import './你copy的目录/native-config.js' //你需要将 /app-support-sample/native-config.js copy到你自己项目中,再引入

在微信小程序中使用

请直接参考本目录下的 miniProgram-wx 小程序demo项目;如果是在小程序web-view中录音请直接使用Recorder H5就行,不需要额外支持。

javascript
//微信开发者工具对npm支持太差,因此最好直接git clone源码使用

//引入RecordApp的微信小程序支持文件
require("./你clone的目录/src/app-support/app-miniProgram-wx-support.js");

在uni-app中使用

请直接参考本目录下的 demo_UniApp demo项目,可编译成:H5、Android App、iOS App、微信小程序,[AD]编译成App时仅供测试,正式发布需商用授权。

在uni-app中使用,需要先导入Recorder-UniCore组件,在demo_UniApp项目中可以找到此组件,或者到DCloud 插件市场下载此组件。

javascript
//所有平台必须引入的uni-app支持文件
import '@/uni_modules/Recorder-UniCore/app-uni-support.js'

//需要编译成微信小程序时,引入微信小程序支持文件
import 'recorder-core/src/app-support/app-miniProgram-wx-support.js'

//需要编译成app时,需要在页面内提供一个renderjs模块,同样的进行一遍import
//具体使用请参考demo_UniApp项目

​

​

二、调用录音

​RecordApp的基础调用方式在所有平台环境下是通用的;但不同环境下可能会提供更多的方法、或配置参数以供使用,多出来的请参考对应的平台环境支持说明。

javascript
/**请求录音权限,Start调用前至少要调用一次RequestPermission**/
var recReq=function(success){
    //RecordApp.RequestPermission_H5OpenSet={ audioTrackSet:{ noiseSuppression:true,echoCancellation:true,autoGainControl:true } }; //这个是Start中的audioTrackSet配置,在h5中必须提前配置,因为h5中RequestPermission会直接打开录音
    
    RecordApp.RequestPermission(function(){
        //注意:有使用到H5录音时,为了获得最佳兼容性,建议RequestPermission、Start至少有一个应当在用户操作(触摸、点击等)下进行调用
        success&&success();
    },function(msg,isUserNotAllow){//用户拒绝未授权或不支持
        console.log((isUserNotAllow?"UserNotAllow,":"")+"无法录音:"+msg);
    });
};

/**开始录音**/
var recStart=function(success){
    var processTime=0,clearBufferIdx=0;
    
    //开始录音的参数和Recorder的初始化参数大部分相同
    RecordApp.Start({
        type:"mp3",sampleRate:16000,bitRate:16 //mp3格式,指定采样率hz、比特率kbps,其他参数使用默认配置;注意:是数字的参数必须提供数字,不要用字符串;需要使用的type类型,需提前把格式支持文件加载进来,比如使用wav格式需要提前加载wav.js编码引擎
        /*,audioTrackSet:{ //可选,如果需要同时播放声音(比如语音通话),需要打开回声消除(打开后声音可能会从听筒播放,部分环境下(如小程序、uni-app原生接口)可调用接口切换成扬声器外放)
            //注意:H5中需要在请求录音权限前进行相同配置RecordApp.RequestPermission_H5OpenSet后此配置才会生效
            echoCancellation:true,noiseSuppression:true,autoGainControl:true} */
        ,onProcess:function(buffers,powerLevel,bufferDuration,bufferSampleRate,newBufferIdx,asyncEnd){
            //录音实时回调,大约1秒调用12次本回调,buffers为开始到现在的所有录音pcm数据块(16位小端LE)
            //可实时上传(发送)数据,可实时绘制波形,ASR语音识别,使用可参考Recorder
            processTime=Date.now();

            //可实时绘制波形(extensions目录内的waveview.js、wavesurfer.view.js、frequency.histogram.view.js插件功能)
            //wave.input(buffers[buffers.length-1],powerLevel,bufferSampleRate);

            /*实时释放清理内存,用于支持长时间录音;在指定了有效的type时,编码器内部可能还会有其他缓冲,必须同时提供takeoffEncodeChunk才能清理内存,否则type需要提供unknown格式来阻止编码器内部缓冲
            //if(this.clearBufferIdx>newBufferIdx){ this.clearBufferIdx=0 } //变量改到this里面时,重新录音了,这样写可以重置this环境
            for(var i=clearBufferIdx||0;i<newBufferIdx;i++) buffers[i]=null;
            clearBufferIdx=newBufferIdx; */
        }
        ,takeoffEncodeChunk: "不设置"?null: function(chunkBytes){
            //实时编码环境下接管编码器输出,类似于onFrameRecorded,当生成了一块音频数据就会回调
            //chunkBytes为指定录音格式的二进制数据Uint8Array,提供此回调后编码器内将不会保存数据(调用Stop方法获得的音频数据长度将为0),需自行保存数据
        }

        //...  不同环境的专有配置,根据文档按需配置
    },function(){
        //开始录音成功
        success&&success();
        
        //【稳如老狗WDT】可选的,监控是否在正常录音有onProcess回调,如果长时间没有回调就代表录音不正常
        var this_=   RecordApp; //有this就用this,没有就用一个全局对象
        if(RecordApp.Current.CanProcess()){
            var wdt=this_.watchDogTimer=setInterval(function(){
                if(wdt!=this_.watchDogTimer){ clearInterval(wdt); return } //sync
                if(Date.now()<this_.wdtPauseT) return; //如果暂停录音了就不检测:puase时赋值this_.wdtPauseT=Date.now()*2(永不监控),resume时赋值this_.wdtPauseT=Date.now()+1000(1秒后再监控)
                if(Date.now()-(processTime||startTime)>1500){ clearInterval(wdt);
                    console.error(processTime?"录音被中断":"录音未能正常开始");
                    // ... 错误处理,关闭录音,提醒用户
                }
            },1000);
        }else{
            console.warn("当前环境不支持onProcess回调,不启用watchDogTimer"); //目前都支持回调
        }
        var startTime=Date.now(); this_.wdtPauseT=0;
    },function(msg){
        console.log("开始录音失败:"+msg);
    });
};


//暂停录音
var recPause=function(){
    if(RecordApp.GetCurrentRecOrNull()){
        RecordApp.Pause();
        var this_=RecordApp;this_.wdtPauseT=Date.now()*2; //永不监控onProcess超时
        console.log("已暂停");
    }
};
//继续录音
var recResume=function(){
    if(RecordApp.GetCurrentRecOrNull()){
        RecordApp.Resume();
        var this_=RecordApp;this_.wdtPauseT=Date.now()+1000; //1秒后再监控onProcess超时
        console.log("继续录音中...");
    }
};


/**停止录音,清理资源**/
var recStop=function(){
    var this_=RecordApp;this_.watchDogTimer=0; //停止监控onProcess超时
    
    RecordApp.Stop(function(arrayBuffer,duration,mime){
        //arrayBuffer就是录音文件的二进制数据,不同平台环境下均可进行播放、上传
        console.log(arrayBuffer,mime,"时长:"+duration+"ms");
        
        //如果当前环境支持Blob,也可以直接构造成Blob文件对象,和Recorder使用一致
        if(typeof(Blob)!="undefined" && typeof(window)=="object"){
            var blob=new Blob([arrayBuffer],{type:mime});
            console.log(blob, (window.URL||webkitURL).createObjectURL(blob));
        }
    },function(msg){
        console.log("录音失败:"+msg);
    });
};


//这里假设立即运行,只录3秒
recReq(function(){
    recStart(function(){
        setTimeout(recStop,3000);
    });
});

​

​

​

【附】需要进行的权限处理

App的WebView中打开网页进行录音时,如果未使用原生录音,将默认使用Recorder H5进行录音(和普通网页录音没有任何区别),此时必须在RecordApp.RequestPermission前先获取到App的系统录音权限,如果App没有系统录音权限,将无法录音。

Android App录音权限

demo_android目录中提供了Android测试项目源码(如果不想自己打包可以用打包好的apk来测试,demo_android目录下的app-debug.apk.zip,自行去掉.zip后缀)。

在Android App WebView中使用本库来H5录音,需要在App源码中实现以下几部分:

  1. 在AndroidManifest.xml声明需要用到的两个权限,第二个也必须的
xml
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
  1. WebView的WebChromeClient中实现onPermissionRequest网页授权请求

如果未实现此方法,WebView默认会拒绝H5录音权限;onPermissionRequest中需要先获取App的系统录音权限(Activity里面必须先用this.checkSelfPermission检测权限,否则直接this.requestPermissions会造成WebView触发touchcancel打断长按),然后再grant网页权限,可参考MainActivity.java中的实现代码。

注:如果应用的腾讯X5内核,可能还须提供android.permission.CAMERA权限,和调用webView.setWebChromeClientExtension来重写X5的IX5WebChromeClientExtension.onPermissionRequest方法来进行权限处理

  1. 如需后台录音,还需要实现Android后台录音保活服务

自Android 9开始,为了保护用户隐私,锁屏或进入后台一段时间后App可能会被禁止访问麦克风、摄像头等功能,导致无法录音、或录音数据全部是静音,因此需要使用保活机制才能在后台录音,详细请参考demo_android,里面专门有一个章节讲解保活。

iOS App录音权限

demo_ios目录中提供了iOS测试项目源码(需自行用xcode编译运行)。

在iOS App WebView中使用本库来录音,需要在App源码 Info.plist 中声明使用麦克风 NSMicrophoneUsageDescription,无需其他处理,WebView会自己处理好录音权限;注意:iOS App需要在项目Background Modes中勾选Audio才能在后台保持录音,不然App切到后台后立马会停止录音。

iOS 14.3+以上版本才支持WebView中进行H5录音。

iOS 15+以上版本提供了静默授权支持,需要WKWebView的uiDelegate实现WKUIDelegate requestMediaCapturePermissionFor接口,可参考iOS Demo中的MainView.swift中的代码;如果未实现,会导致H5录音每次打开页面后第一次录音时、或长时间无操作再打开录音时均会弹出录音权限对话框。

iOS 11.0-14.2:纯粹的H5录音在iOS WebView中是不支持的,需要有Native层的支持,具体参考demo_ios测试项目源码。

微信小程序录音权限

录音功能由微信小程序的RecorderManager提供,需要用户授予录音权限,调用RecordApp.RequestPermission的时候会检查是否能正常录音,如果用户拒绝了录音权限,会进入错误回调,回调里面你应当编写代码检查wx.getSetting中的scope.record录音权限,然后引导用户进行授权(可调用wx.openSetting打开设置页面,方便用户给权限)。

Electron程序录音权限

在Electron BrowserWindow中使用本库来录音,需要程序本身先获得录音权限,调用RecordApp.RequestPermission前先到主进程使用 systemPreferences.getMediaAccessStatus('microphone') 判断程序是否有录音权限,如果是未申请权限,需要调用 systemPreferences.askForMediaAccess('microphone') 来请求权限;程序有权限后,即可和普通网页一样正常的录音。

uni-app录音权限

uni-app项目当需要编译成Android、iOS App时,需要在 manifest.json 中配置好Android和iOS的录音权限声明,参考上面的Android iOS权限,是一样的,注意Android不要漏掉了MODIFY_AUDIO_SETTINGS权限;无需其他处理,因为Recorder-UniCore组件内已经全部处理好了。

​

​

【QQ群】交流与支持

欢迎加QQ群:①群 781036591、②群 748359095、③群 450721519、④群 1027243616,纯小写口令:recorder

​

​

​

​

:open_book:方法文档

【静态方法】RecordApp.RequestPermission(success,fail)

请求录音权限,如果当前环境不支持录音或用户拒绝将调用错误回调;调用RecordApp.Start前需先至少调用一次此方法,用于获得权限和必要的环境初始化;请求权限后如果不使用了,不管有没有调用Start,至少要调用一次Stop来清理可能持有的资源。

success: fn() 有权限时回调

fail: fn(errMsg,isUserNotAllow) 没有权限或者不能录音时回调,如果是用户主动拒绝的录音权限,除了有错误消息外,isUserNotAllow=true,方便程序中做不同的提示,提升用户主动授权概率

注意:H5等环境中在请求权限时会调用Recorder的open方法打开录音,并保持打开直到Start时直接录音,会导致部分open会用到的配置参数在Start时不会生效,这些配置参数必须在请求录音权限前配置到RecordApp.RequestPermission_H5OpenSet={...}里面才会生效,主要为audioTrackSet、runningContext、sourceStream配置。

【静态方法】RecordApp.Start(set,success,fail)

开始录音,需先调用RecordApp.RequestPermission;成功开始录音后会回调success(),失败会回调fail(errMsg)。

javascript
set配置默认值(和Recorder的初始化参数大部分相同):
{
    type:"mp3" //输出类型:mp3,wav等,使用一个类型前需要先引入对应的编码引擎
    ,bitRate:16 //比特率,必须是数字 wav(位):16、8,MP3(单位kbps):8kbps时文件大小1k/s,16kbps 2k/s,录音文件很小
    
    ,sampleRate:16000 //采样率,必须是数字,wav格式(8位)文件大小=sampleRate*时间;mp3此项对低比特率文件大小有影响,高比特率几乎无影响。
                //wav任意值,mp3取值范围:48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000
    
    ,onProcess:NOOP //接收到录音数据时的回调函数:fn(buffers,powerLevel,bufferDuration,bufferSampleRate,newBufferIdx,asyncEnd)
            //如果某平台环境下没有特殊说明,那么此环境下的回调和Recorder的onProcess回调行为完全一致
                //返回值:onProcess如果返回true代表开启异步模式,在某些大量运算的场合异步是必须的,必须在异步处理完成时调用asyncEnd(不能真异步时需用setTimeout包裹);返回其他值或者不返回为同步模式(需避免在回调内执行耗时逻辑);如果开启异步模式,在onProcess执行后新增的buffer会全部替换成空数组,因此本回调开头应立即将newBufferIdx到本次回调结尾位置的buffer全部保存到另外一个数组内,处理完成后写回buffers中本次回调的结尾位置。
                //buffers=[[Int16,...],...]:缓冲的PCM数据块(16位小端LE),为从开始录音到现在的所有pcm片段,每次回调可能增加0-n个不定量的pcm片段。
                    //注意:buffers数据的采样率为bufferSampleRate,它和set.sampleRate不一定相同,可能为浏览器提供的原始采样率rec.srcSampleRate,也可能为已转换好的采样率set.sampleRate;如需浏览器原始采样率的数据,请使用rec.buffers原始数据,而不是本回调的参数;如需明确和set.sampleRate完全相同采样率的数据,请在onProcess中自行连续调用采样率转换函数Recorder.SampleData(),配合mock方法可实现实时转码和压缩语音传输;修改或替换buffers内的数据将会改变最终生成的音频内容(注意不能改变第一维数组长度),比如简单有限的实现实时静音、降噪、混音等处理,详细参考下面的rec.buffers
                //powerLevel:当前缓冲的音量级别0-100。
                //bufferDuration:已缓冲时长。
                //bufferSampleRate:buffers缓存数据的采样率(当type支持边录边转码(Worker)时,此采样率和设置的采样率相同,否则不一定相同)。
                //newBufferIdx:本次回调新增的buffer起始索引。
                //asyncEnd:fn() 如果onProcess是异步的(返回值为true时),处理完成时需要调用此回调,如果不是异步的请忽略此参数,此方法回调时必须是真异步(不能真异步时需用setTimeout包裹)。
                //如果需要绘制波形之类功能,需要实现此方法即可,使用以计算好的powerLevel可以实现音量大小的直观展示,使用buffers可以达到更高级效果
                //如果需要实时上传(发送)之类的,可以配合Recorder.SampleData方法,将buffers中的新数据连续的转换成pcm,或使用mock方法将新数据连续的转码成其他格式,可以参考文档里面的:Demo片段列表 -> 实时转码并上传-通用版;基于本功能可以做到:实时转发数据、实时保存数据、实时语音识别(ASR)等
                //如果需要长时间录音,比如超过1小时,需要在onProcess内实时释放清理内存,参考实时上传中有释放代码,需要自行实现数据的保存
    
    //*******高级设置******
        //,disableEnvInFix:false 内部参数,禁用设备卡顿时音频输入丢失补偿功能,如果不清楚作用请勿随意使用

        //,allowUpsample:false 当源采样率this.srcSampleRate<set.sampleRate(如set=48000但源只有44100)时是否要提升采样率,260708版本新增
            //默认不允许,同时set.sampleRate会被重设为源采样率;比如用最高音质录,但实际只取16k的pcm,默认false不影响音质;如果确实需要提升请设为true
            //提升采样率的音质依赖重采样算法和计算复杂度,内置的SampleData+IIRFilter会引入轻微杂音

        //,engine_mp3_lowpassfreq:0 mp3专用配置,16kbps时配置成-1声音不沉闷可提升清晰度,但会增加噪声,详细请阅读Recorder文档中mp3编码器说明

        //,takeoffEncodeChunk:NOOP //fn(chunkBytes) chunkBytes=[Uint8,...]:实时编码环境下接管编码器输出,类似于onFrameRecorded,当编码器实时编码出一块有效的二进制音频数据时实时回调此方法;参数为二进制的Uint8Array,就是编码出来的音频数据片段,所有的chunkBytes拼接在一起即为完整音频。本实现的想法最初由QQ2543775048提出。
                //当提供此回调方法时,将接管编码器的数据输出,编码器内部将放弃存储生成的音频数据;环境要求比较苛刻:如果当前环境不支持实时编码处理,将在Start时直接走fail逻辑
                //因此提供此回调后调用Stop方法将无法获得有效的音频数据,因为编码器内没有音频数据,因此Stop时返回的数据长度为0
    
    //*******H5录音专有配置,部分参数在别的环境下可能也会生效******
        //,sourceStream:MediaStream Object
                //可选直接提供一个媒体流,从这个流中录制、实时处理音频数据,请参考Recorder

        //,runningContext:AudioContext
                //可选提供一个state为running状态的AudioContext对象(ctx),请参考Recorder

        /*,audioTrackSet:{ //请参考Recorder,echoCancellation在微信小程序、uni-app也可用;注意:H5等环境中需要在请求录音权限前进行相同配置RecordApp.RequestPermission_H5OpenSet后此配置才会生效
             deviceId:"",groupId:"" //指定设备的麦克风,通过navigator.mediaDevices.enumerateDevices拉取设备列表,其中kind为audioinput的是麦克风
            ,echoCancellation:true //回声消除(AEC)开关,不设置时由浏览器控制(一般为默认自动打开),设为true明确打开,设为false明确关闭
            ,noiseSuppression:true //降噪(ANS)开关,取值和回声消除开关一样
            ,autoGainControl:true //自动增益(AGC)开关,取值和回声消除开关一样
        }*/
    
    //*******uni-app编译成app时专有配置,详细请参考 demo_UniApp 目录******
        //,onProcess_renderjs:'function(...){ }' //renderjs中执行的onProcess函数字符串
        //,takeoffEncodeChunk_renderjs:'function(...){ }' //renderjs中执行的takeoffEncodeChunk函数字符串
        //,start_renderjs:'function(){ }' //Start成功时renderjs中会先调用这个函数字符串
        //,stop_renderjs:'function(...){ }' //Stop成功时renderjs中会先调用这个函数字符串
}
注意:此对象会被修改,因为平台实现时需要把实际使用的采样率、比特率存入此对象

【静态方法】RecordApp.Stop(success,fail)

结束录音和清理资源。

success: fn(arrayBuffer,duration,mime) 结束录音时回调,arrayBuffer:ArrayBuffer 录音二进制数据,duration: 123 音频持续时间毫秒,mime: audio/mp3 录音格式类型。

fail: fn(errMsg) 录音出错时回调

如果不提供success参数=null时,将不会进行音频编码操作,只进行清理完可能持有的资源后走fail回调。

注意:当Start时提供了takeoffEncodeChunk后,你需要自行实时保存录音文件数据,因此Stop时返回的arrayBuffer的长度将为0字节。

【静态方法】RecordApp.Pause()

暂停录音。

【静态方法】RecordApp.Resume()

继续录音。

【静态方法】RecordApp.Install(success,fail)

对底层平台环境进行识别和初始化,RecordApp.RequestPermission只是对此方法进行了一次封装,并且多了一个权限请求而已。如果你只想先完成环境初始化,并不想调起权限请求,可手动调用此方法。此方法可以反复调用。

success: fn() 初始化成功回调

fail: fn(errMsg) 初始化失败回调

【静态属性】RecordApp.Current

为RecordApp.Install初始化后识别到的底层平台环境,取值为RecordApp.Platforms之一。

【静态方法】RecordApp.Current.CanProcess()

识别的底层平台环境是否支持实时返回PCM数据,如果返回值为true,set.onProcess将可以被实时回调,目前所有环境均为true。

【静态方法】RecordApp.GetCurrentRecOrNull()

获取底层平台录音过程中会使用用来处理实时数据的Recorder对象实例rec,如果底层录音过程中不使用Recorder进行数据的实时处理(目前没有),将返回null。Start调用前和Stop调用后均会返回null。

rec中的方法不一定都能使用,主要用来获取内部缓冲用的,比如实时清理缓冲。

【静态属性】RecordApp.Platforms

当前已注册支持的平台列表,引入相关平台支持文件后(app-xxx-support.js),会自动通过RecordApp.RegisterPlatform注册进这个列表。

【静态方法】RecordApp.RegisterPlatform(key,config)

注册一个平台的实现,对应的都会有一个app-xxx-support.js支持文件(Default-H5除外),config中提供统一的实现接口,实现比较复杂,具体请阅读此方法源码注释,和参考已有的app-xxx-support.js文件。

当你使用的平台环境没有相应的js支持文件时,你可以调用这个方法自行进行对接实现,然后就能正常使用RecordApp进行录音了。

​

​

​

​

:open_book:不同平台环境下的使用说明

内置:Default-H5

这个是RecordApp的内置支持,底层由Recorder H5提供录音支持;当在浏览器(WebView)环境中使用时,如果没有其他环境支持,就会默认由H5进行录音。

可使用的Recorder插件

因为是在浏览器中使用,因此所有的Recorder插件均可以使用。

​

​

App原生录音:app-native-support.js

app-native-support.js 是App Native原生录音支持文件,支持在浏览器环境中使用(Android iOS WebView)、各种适配后的js运行环境中使用(非浏览器环境);使用本文件需要iOS、Android等App端提供的原生支持,如果你不能修改App的源码,将无法提供原生录音支持。

录音功能由原生App(Native)代码实现,通过JsBridge和js进行交互。Native层需要提供:请求权限、开始录音、结束录音、定时回调PCM[Int16]片段 等功能和接口。因为js层已加载Recorder和相应的js编码引擎,所以,Native层无需进行编码,可大大简化App的逻辑。

JsBridge可以是自己实现的交互方式 或 别人提供的框架。因为不知道具体使用的桥接方式,对应的请求已抽象成了4个方法在Native.Config中,需自行实现;你可以直接参考使用本文档目录下的 native-config.js 配置文件,还有参考 demo_android 和 demo_ios 两个demo项目,copy源码改改就能用。

可使用的Recorder插件

浏览器环境中使用时,所有的Recorder插件均可以使用;非浏览器环境中部分依赖浏览器的插件将不可用(如:可视化、播放器)。

【静态属性】RecordApp.AlwaysAppUseH5

默认为false,设为true时:App里面总是使用H5网页版录音,忽略原生录音。

​

​

微信小程序:app-miniProgram-wx-support.js

app-miniProgram-wx-support.js 是微信小程序支持文件,支持在微信小程序环境中使用;使用请参考本目录内的 miniProgram-wx 小程序demo项目;如果是在小程序web-view中录音请直接使用Recorder H5就行,不需要额外支持。

录音功能由微信小程序的RecorderManager录音接口提供(已屏蔽10分钟录音限制),因为js层已加载Recorder和相应的js编码引擎,所以,Recorder支持的录音格式,小程序内均可以做到支持。

可使用的Recorder插件

所有可视化、ASR插件均支持在小程序内使用,其他部分依赖浏览器环境的插件不可用(如:播放器)。

【静态方法】RecordApp.MiniProgramWx_onShow()

当使用到录音的页面onShow时进行一次调用,用于恢复被暂停的录音(比如按了home键会暂停录音)。

小程序自带的RecorderManager录音的时候,如果小程序退到了后台(或触发了onInterruptionBegin),录音将会被暂停,小程序显示的时候才允许继续录音,但RecorderManager无法感知这些事件或者存在bug,因此你需要在要录音页面的onShow函数内加上RecordApp.MiniProgramWx_onShow()这行代码,如果没有加这行代码,被暂停的录音可能会无法自动恢复。

​

​

uni-app组件:app-uni-support.js

在本文档目录下的demo_UniApp项目中可以找到Recorder-UniCore组件,或者到DCloud 插件市场下载此组件,路径为 @/uni_modules/Recorder-UniCore/app-uni-support.js 。

此组件支持vue2、vue3、nvue,支持编译成:H5、Android App、iOS App、微信小程序,[AD]编译成App时仅供测试,正式发布需商用授权。

app-uni-support.js 在RecordApp下提供了大量的方法可供调用,详细文档请阅读demo_UniApp的文档。

可使用的Recorder插件

编译成H5时,所有插件均可使用。

编译成App时,在renderjs支持使用所有插件(renderjs就是WebView浏览器环境);在逻辑层中支持使用所有可视化、ASR插件,其他部分依赖浏览器环境的插件不可用(如:播放器)。

编译成小程序时,所有可视化插件均支持在小程序内使用,其他部分依赖浏览器环境的插件不可用(如:播放器)。

​

​

​

​

:open_book:国际化多语言支持(i18n)

RecordApp共用Recorder.i18n实现,因此只需配置Recorder的语言即可;支持的语言文件在 /src/i18n 目录内,详细使用请参考Recorder文档。

比如切换成英文:先import "recorder-core/src/i18n/en-US.js",然后配置Recorder.i18n.lang="en-US"即可。

不同平台环境下可能还有专门的i18n文件夹,请参考对应的说明文档,import对应的语言文件进来即可。

​

​

​

​

:star:捐赠

如果这个库有帮助到您,请 Star 一下。

您也可以使用支付宝或微信打赏作者:


File: app-support-sample/demo_android/README.md

Recorder | RecordApp

:open_book:Android Hybrid App

目录内包含Android App测试源码,和核心文件 RecordAppJsBridge.java ;目录内 app-debug.apk.zip 为打包好的debug包(40kb,删掉.zip后缀),或者clone后自行用Android Studio编译打包。本demo为java代码,兼容API Level 15+,已测试API Level 35(Android 15)。

本Demo是对/app-support-sample/native-config.js配置示例中定义的JsBridge接口的实现,支持H5录音、和原生接口录音。

可以直接copy目录内RecordAppJsBridge.java使用,此文件为核心文件;支持新开发WebView界面,或对已有的WebView实例升级支持RecordApp。

【限制】

  • 虽然兼容API Level 15+,但实际上17+才是好的选择,因为addJavascriptInterface
  • 古董WebView(21-)虽然能正常接收PCM和进行MP3、WAV编码,但对Blob对象的播放不一定能良好支持
  • 如需后台录音,H5录音、原生接口录音均需要提供保活机制,参考下面的录音保活

需要权限

  1. android.permission.RECORD_AUDIO
  2. android.permission.MODIFY_AUDIO_SETTINGS 这个权限也是必须的
  3. 如需后台录音,后台录音保活服务会涉及到多个权限,H5录音、原生接口录音均需要

注意:仅使用H5录音时,需要WebView的WebChromeClient中实现onPermissionRequest网页授权请求,如果未实现此方法,WebView默认会拒绝H5录音权限;onPermissionRequest中需要先获取App的系统录音权限(Activity里面必须先用this.checkSelfPermission检测权限,否则直接this.requestPermissions会造成WebView触发touchcancel打断长按),然后再grant网页权限,可参考MainActivity.java中的实现代码。

注:如果使用的腾讯X5内核,可能还须提供android.permission.CAMERA权限,和调用webView.setWebChromeClientExtension来重写X5的IX5WebChromeClientExtension.onPermissionRequest方法来进行权限处理

Android后台录音保活

自Android 9开始,为了保护用户隐私,锁屏或进入后台一段时间后App可能会被禁止访问麦克风、摄像头等功能,导致无法录音、或录音数据全部是静音,因此需要使用保活机制才能在后台录音;H5录音、原生接口录音均受影响。iOS不存在此类问题,iOS只要在manifest.json的后台运行能力中提供audio配置,即可在后台录音。

如需后台录音就需要使用一个前台服务(Foreground services)来作为搭配常驻通知的Android后台录音保活服务,打开服务后可提升App在后台或锁屏后录音的稳定性,服务会在状态栏显示一个常驻的通知(通知可能不显示或会延迟显示,并不影响服务运行);不同机型的录音保活效果不一定100%有效,目前本Android Demo代码中未提供此服务实现,需要自行实现、或咨询作者有偿提供。

【注意】targetSdkVersion>=31(Android 12+)时,App必须在前台才能调用启动此前台服务,在后台时不允许调用;targetSdkVersion>=34(Android 14+)时,microphone类型的服务依赖录音权限,因此建议先请求录音权限(调用RecordApp.RequestPermission请求权限),成功后再来调用启动服务的接口。

原理

通过addJavascriptInterface往WebView注入一个全局对象RecordAppJsBridge,js中通过window.RecordAppJsBridge来访问,只有存在这个对象,就代表是在App中;js通过这个对象的request方法和java进行数据的交互。

数据交互

java收到js发起的RecordAppJsBridge.request请求,解析请求数据参数,并调用参数中接口对应的java方法,同步执行完后把数据返回给js,如果方法是异步的,将在异步操作完成后java将调用网页的js方法AppJsBridgeRequest.Call将数据异步返回(如果是跨域的iframe中发起的异步调用,数据将会使用postMessage来转发返回)。

录音接口

接口对应的方法使用的AudioRecord来录音,AudioRecord使用稳健的44100采样率进行音频采集,我们实时接收PCM数据并进行采样率的转换,然后调用AppJsBridgeRequest.Record把数据返回给js端即可完成完整的录音功能。

【截图】


File: app-support-sample/demo_ios/README.md

Recorder | RecordApp

:open_book:iOS Hybrid App

本目录内包含iOS App测试源码,和核心文件 RecordAppJsBridge.swift ;clone后用xcode打开后编译运行。本demo为swift代码,兼容iOS 9.0+,已测试iOS 17.1。

本Demo是对/app-support-sample/native-config.js配置示例中定义的JsBridge接口的实现,支持H5录音、和原生接口录音。

可以直接copy目录内RecordAppJsBridge.swift使用,此文件为核心文件;支持新开发WKWebView界面,或对已有的WKWebView实例升级支持RecordApp。

【限制】

  • xcode测试项目clone后请修改PRODUCT_BUNDLE_IDENTIFIER,不然这个测试id被抢来抢去要闲置7天才能被使用,太难了
  • 仅H5录音需要iOS 14.3+以上的系统才支持,低于14.3必须使用原生接口
  • 仅H5录音需要iOS 15+以上才支持录音静默授权,如果WebView未适配静默授权、或版本低于15,会导致WebView每次打开后第一次录音时、或长时间无操作再打开录音时均会弹出录音权限对话框,使用原生接口无此问题
  • 未做古董版本UIWebView适配,理论上并不需要太大改动就能支持,并不打算进行支持、也没有必要进行支持
  • 未测试在OC中调用此swift文件,可参考代码自行改写成OC代码

需要权限

需要在App源码 Info.plist 中声明使用麦克风 NSMicrophoneUsageDescription,无需其他处理,WebView会自己处理好录音权限;注意:iOS App需要在项目Background Modes中勾选Audio才能在后台保持录音,不然App切到后台后立马会停止录音。

注意:仅使用H5录音时(不使用原生录音接口),需要WKWebView的uiDelegate实现WKUIDelegate requestMediaCapturePermissionFor接口(iOS 15+支持)才能做到静默授权支持,可参考MainView.swift中的代码;如果未实现,会导致H5录音每次打开页面后第一次录音时、或长时间无操作再打开录音时均会弹出录音权限对话框。

原理

通过userContentController往WKWebView注入一个全局对象RecordAppJsBridgeIsSet,js中通过webkit.messageHandlers.RecordAppJsBridgeIsSet来访问,只有存在这个对象,就代表是在App中;但并不通过这个对象来进行数据交互,因为它仅支持异步操作;数据交互需要一个同步方法来进行支持,因为同步可以实现异步,仅支持异步的只能异步到底,所以选择重写WebView的prompt弹框方法,进行数据的交互。

数据交互

swift收到js发起的prompt弹框请求,解析弹框携带的数据参数,并调用参数中接口对应的swift方法,同步执行完后把数据返回给prompt弹框,如果方法是异步的,将在异步操作完成后swift将调用网页的js方法AppJsBridgeRequest.Call将数据异步返回(如果是跨域的iframe中发起的异步调用,数据将会使用postMessage来转发返回)。

录音接口

接口对应的方法使用的AudioUnit来录音,AudioUnit会实时回调录音PCM数据,然后调用AppJsBridgeRequest.Record把数据返回给js端即可完成完整的录音功能。

早期版本(2020-11-17)接口对应的方法使用的AVAudioRecorder来录音,AVAudioRecorder会把录音PCM数据写入到文件,因此我们实时从这个文件中读取出数据,然后定时调用AppJsBridgeRequest.Record把数据返回给js端即可完成完整的录音功能;可能是因为AVAudioRecorder存在文件写入缓存的原因,数据并非实时的flush到文件的,因此实时发送给js的数据存在300ms左右的滞后,没有AudioUnit的好使。

运行截图


File: app-support-sample/demo_ios/recorder/ios/Assets.xcassets/Contents.json

{
"info" : {
"version" : 1,
"author" : "xcode"
}
}


File: app-support-sample/demo_ios/recorder/ios/Assets.xcassets/AppIcon.appiconset/Contents.json

{
"images": [
{
"size": "20x20",
"idiom": "iphone",
"filename": "[email protected]",
"scale": "2x"
},
{
"size": "20x20",
"idiom": "iphone",
"filename": "[email protected]",
"scale": "3x"
},
{
"size": "29x29",
"idiom": "iphone",
"filename": "icon-29.png",
"scale": "1x"
},
{
"size": "29x29",
"idiom": "iphone",
"filename": "[email protected]",
"scale": "2x"
},
{
"size": "29x29",
"idiom": "iphone",
"filename": "[email protected]",
"scale": "3x"
},
{
"size": "40x40",
"idiom": "iphone",
"filename": "[email protected]",
"scale": "2x"
},
{
"size": "40x40",
"idiom": "iphone",
"filename": "[email protected]",
"scale": "3x"
},
{
"size": "57x57",
"idiom": "iphone",
"filename": "icon-57.png",
"scale": "1x"
},
{
"size": "57x57",
"idiom": "iphone",
"filename": "[email protected]",
"scale": "2x"
},
{
"size": "60x60",
"idiom": "iphone",
"filename": "[email protected]",
"scale": "2x"
},
{
"size": "60x60",
"idiom": "iphone",
"filename": "[email protected]",
"scale": "3x"
},
{
"size": "20x20",
"idiom": "ipad",
"filename": "icon-20-ipad.png",
"scale": "1x"
},
{
"size": "20x20",
"idiom": "ipad",
"filename": "[email protected]",
"scale": "2x"
},
{
"size": "29x29",
"idiom": "ipad",
"filename": "icon-29-ipad.png",
"scale": "1x"
},
{
"size": "29x29",
"idiom": "ipad",
"filename": "[email protected]",
"scale": "2x"
},
{
"size": "40x40",
"idiom": "ipad",
"filename": "icon-40.png",
"scale": "1x"
},
{
"size": "40x40",
"idiom": "ipad",
"filename": "[email protected]",
"scale": "2x"
},
{
"size": "50x50",
"idiom": "ipad",
"filename": "icon-50.png",
"scale": "1x"
},
{
"size": "50x50",
"idiom": "ipad",
"filename": "[email protected]",
"scale": "2x"
},
{
"size": "72x72",
"idiom": "ipad",
"filename": "icon-72.png",
"scale": "1x"
},
{
"size": "72x72",
"idiom": "ipad",
"filename": "[email protected]",
"scale": "2x"
},
{
"size": "76x76",
"idiom": "ipad",
"filename": "icon-76.png",
"scale": "1x"
},
{
"size": "76x76",
"idiom": "ipad",
"filename": "[email protected]",
"scale": "2x"
},
{
"size": "83.5x83.5",
"idiom": "ipad",
"filename": "[email protected]",
"scale": "2x"
},
{
"size": "1024x1024",
"idiom": "ios-marketing",
"filename": "icon-1024.png",
"scale": "1x"
}
],
"info": {
"version": 1,
"author": "xiangyuecn"
}
}


File: app-support-sample/demo_UniApp/README.md

[Binary file]


File: app-support-sample/demo_UniApp/manifest.json

{
"name" : "RecordAppTest",
"appid" : "",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
/* 5+App特有相关 /
"app-plus" : {
"usingComponents" : true,
"nvueStyleCompiler" : "uni-app",
"compilerVersion" : 3,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
/
模块配置 /
"modules" : {},
/
应用发布信息 /
"distribute" : {
/
android打包配置 /
"android" : {
"permissions" : [
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
""
]
},
/
ios打包配置 /
"ios" : {
"privacyDescription" : {
"NSMicrophoneUsageDescription" : "录音功能需要访问麦克风"
},
"UIBackgroundModes" : "audio"
},
/
SDK配置 /
"sdkConfigs" : {}
}
},
/
快应用特有相关 /
"quickapp" : {},
/
小程序特有相关 */
"mp-weixin" : {
"appid" : "",
"setting" : {
"urlCheck" : false
},
"usingComponents" : true
},
"mp-alipay" : {
"usingComponents" : true
},
"mp-baidu" : {
"usingComponents" : true
},
"mp-toutiao" : {
"usingComponents" : true
},
"uniStatistics" : {
"enable" : false
},
"vueVersion" : "3"
}


File: app-support-sample/demo_UniApp/package.json

{
"name": "recorder-demo_UniApp",
"version": "1.0.0",
"dependencies": {
"recorder-core": "*"
}
}


File: app-support-sample/demo_UniApp/pages.json

{
"pages": [ //pages数组中第一项表示应用启动页,参考:https://uniapp.dcloud.io/collocation/pages
{
"path" : "pages/recTest/page_index",
"style" : {
"navigationBarTitleText": "RecordApp录音测试 - uni-app"
}
},
{
"path" : "pages/recTest/page_index2",
"style" : {
"navigationBarTitleText": "RecordApp新开页面index - uni-app"
}
},
{
"path" : "pages/recTest/main_recTest",
"style" : {
"navigationBarTitleText": "RecordApp新开页面main - uni-app"
}
},
{
"path" : "pages/recTest/page_asr",
"style" : {
"navigationBarTitleText": "RecordApp语音识别ASR - uni-app"
}
},
{
"path" : "pages/recTest/page_i18n",
"style" : {
"navigationBarTitleText": "RecordApp国际化多语言 - uni-app"
}
},
{
"path" : "pages/recTest/page_nvue",
"style" : {
"navigationBarTitleText": "RecordApp nvue原生页面 - uni-app"
}
},
{
"path" : "pages/recTest/page_renderjsOnly",
"style" : {
"navigationBarTitleText": "RecordApp纯renderjs调用 - uni-app"
}
},
{
"path" : "pages/recTest/page_renderjsWithout",
"style" : {
"navigationBarTitleText": "后台录音更稳:逻辑层编码UniWithoutAppRenderjs - uni-app"
}
},
// #ifdef VUE3
{
"path" : "pages/recTest/vue3ComponentTest/vue3_Page",
"style" : {
"navigationBarTitleText": "RecordApp vue3子组件内可用性测试 - uni-app"
}
},
{
"path" : "pages/recTest/vue3ComponentTest/vue3_Page_MultiRoot_CompositionAPI",
"style" : {
"navigationBarTitleText": "vue3多个根节点兼容性测试 - 组合式Composition API页面 - uni-app"
}
},
{
"path" : "pages/recTest/vue3ComponentTest/vue3_Page_MultiRoot_OptionsAPI",
"style" : {
"navigationBarTitleText": "vue3多个根节点兼容性测试 - 选项式Options API页面 - uni-app"
}
},
// #endif
{
"path" : "pages/recTest/page_vue3____composition_api",
"style" : {
"navigationBarTitleText": "RecordApp vue3组合式api测试 - uni-app"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
},
"uniIdRouter": {}
}


File: app-support-sample/demo_UniApp/static/__empty__.md

空的一个文件夹


File: app-support-sample/demo_UniApp/uni_modules/Recorder-UniCore/readme.md

⠀

⠀

Recorder-UniCore组件:uni-app内使用RecordApp录音

本组件使用Recorder开源库来进行录音和音频数据处理,使用RecordApp和本组件内的app-uni-support.js来适配到不同平台环境下进行录音。

  • 支持vue2、vue3、nvue
  • 支持编译成:H5、Android App、iOS App、鸿蒙App、微信小程序
  • 支持已有的大部分录音格式:mp3、wav、pcm、amr、ogg、g711a、g711u等
  • 支持实时处理,包括变速变调、实时上传、ASR语音转文字
  • 支持可视化波形显示;可配置回声消除、降噪;注意:不支持通话时录音
  • 支持PCM音频流式播放、完整播放,App端用原生插件边录音边播放更流畅
  • 支持离线使用,本组件和配套原生插件均不依赖网络
  • App端有配套的原生录音插件可供搭配使用,兼容性和体验更好(暂无鸿蒙原生)

详细文档(含Demo项目): https://github.com/xiangyuecn/Recorder/tree/master/app-support-sample/demo_UniApp

Recorder开源库地址: https://github.com/xiangyuecn/Recorder

如果github打不开,可以点此访问Gitee仓库地址 。

⠀

测试方法

示例项目如果在HBuilder中编译失败,请删掉node_modules目录重新手动执行npm install(偶尔出现HBuilder自动创建项目依赖包不完整,导致无法编译)

  1. 在本插件市场页面右侧下载或导入示例项目(或打开上面详细文档链接中的Demo源码)
  2. 在测试项目根目录执行 npm install --registry=https://registry.npmmirror.com/ ,完成recorder-core依赖的安装
  3. 在HBuilder中打开本测试项目文件夹
  4. 在HBuilder中运行到浏览器、手机、微信小程序,即可在不同环境下测试
  5. 测试中提供了:基础录音、播放、上传、WebSocket实时语音通话对讲、ASR语音识别等功能

⠀

⠀

集成到自己项目中

你可以直接参考上面的测试示例项目源码,里面的main_recTest.vue更容易入门;示例项目中已经实现了很多功能,简单使用可直接照抄Demo代码到你的项目中。

一、引入js文件

  1. 在你的项目根目录安装recorder-core:npm install recorder-core --registry=https://registry.npmmirror.com/
  2. 导入Recorder-UniCore组件:插件市场下载本组件,然后添加到你的项目中 /uni_modules/Recorder-UniCore
  3. 项目配置好录音权限,参考下面的录音权限配置章节,特别注意App后台录音配置、小程序权限声明
  4. 在需要录音的vue文件script内编写以下代码,按需引入需要的js
html
<template>
    <view>
        ... 建议template下只有一个根节点(最外面套一层view),如果不小心踩到了vue3的Fragments(multi-root 多个根节点)特性(vue2编译会报错,vue3不会),可能会出现奇奇怪怪的兼容性问题 
    </view>
</template>

<script> /**这里是逻辑层**/
//必须引入的Recorder核心(文件路径是 /src/recorder-core.js 下同),使用import、require都行
import Recorder from 'recorder-core' //注意如果未引用Recorder变量,可能编译时会被优化删除(如vue3 tree-shaking),请改成 import 'recorder-core',或随便调用一下 Recorder.a=1 保证强引用

//必须引入的RecordApp核心文件(文件路径是 /src/app-support/app.js)
import RecordApp from 'recorder-core/src/app-support/app'

//所有平台必须引入的uni-app支持文件(如果编译出现路径错误,请把@换成 ../../ 这种)
import '@/uni_modules/Recorder-UniCore/app-uni-support.js'


// #ifdef APP-HARMONY
//鸿蒙App适配:需要配置配套的UTS原生组件,来实现权限请求和文件保存。尽量加上条件编译,不加的话Android、iOS也会编译这个UTS又用不到。可到demo示例项目中复制,或插件市场下载此配套UTS组件 https://ext.dcloud.net.cn/plugin?name=Recorder-UniHarmony 
import * as UniHarmonyUTS from '@/uni_modules/Recorder-UniHarmony';
RecordApp.UniHarmonyUTS=UniHarmonyUTS;
// #endif


/** 需要编译成微信小程序时,引入微信小程序支持文件 **/
// #ifdef MP-WEIXIN
    import 'recorder-core/src/app-support/app-miniProgram-wx-support.js'
// #endif


/** H5、小程序环境中:引入需要的格式编码器、可视化插件,App环境中默认是在renderjs中引入 **/
// 注意:如果App中配置了 RecordApp.UniWithoutAppRenderjs=true 时,需要去掉此条件编译,否则会报未加载编码器的错误
// #ifdef H5 || MP-WEIXIN
    //按需引入你需要的录音格式支持文件,如果需要多个格式支持,把这些格式的编码引擎js文件统统引入进来即可
    import 'recorder-core/src/engine/mp3'
    import 'recorder-core/src/engine/mp3-engine' //如果此格式有额外的编码引擎(*-engine.js)的话,必须要加上
    
    //可选的插件支持项,把需要的插件按需引入进来即可
    import 'recorder-core/src/extensions/waveview'
// #endif

// ... 这后面写页面代码,用选项式API风格(vue2、vue3)、setup组合式API风格(仅vue3)都可以
</script>
  1. 编译成app时,默认需要额外提供一个renderjs模块,请照抄下面这段代码放到vue文件末尾
html
<!-- #ifdef APP -->
<script module="recModule" lang="renderjs"> //此模块内部只能用选项式API风格,vue2、vue3均可用,请照抄这段代码;不可改成setup组合式API风格,否则可能不能import vue导致编译失败
/**需要编译成App时,你需要添加一个renderjs模块,然后一模一样的import上面那些js(微信的js除外)
    ,因为App中默认是在renderjs(WebView)中进行录音和音频编码
    。如果配置了 RecordApp.UniWithoutAppRenderjs=true 且未调用依赖renderjs的功能时(如nvue、可视化、仅H5中可用的插件)
    ,可不提供此renderjs模块,同时逻辑层中需要将相关import的条件编译去掉**/
import 'recorder-core'
import RecordApp from 'recorder-core/src/app-support/app'
import '../../uni_modules/Recorder-UniCore/app-uni-support.js' //renderjs中似乎不支持"@/"打头的路径,如果编译路径错误请改正路径即可

//按需引入你需要的录音格式支持文件,和插件
import 'recorder-core/src/engine/mp3'
import 'recorder-core/src/engine/mp3-engine' 

import 'recorder-core/src/extensions/waveview'

export default {
    mounted(){
        //App的renderjs必须调用的函数,传入当前模块this
        RecordApp.UniRenderjsRegister(this);
    },
    methods: {
        //这里定义的方法,在逻辑层中可通过 RecordApp.UniWebViewVueCall(this,'this.xxxFunc()') 直接调用
        //调用逻辑层的方法,请直接用 this.$ownerInstance.callMethod("xxxFunc",{args}) 调用,二进制数据需转成base64来传递
    }
}
</script>
<!-- #endif -->

⠀

⠀

二、调用录音

javascript
/**在逻辑层中编写**/
//import ... 上面那些import代码

//var vue3This=getCurrentInstance().proxy; //当用vue3 setup组合式 API (Composition API) 编写时,直接在import后面取到当前实例this,在需要this的地方传vue3This变量即可,其他的和选项式 API (Options API) 没有任何区别;import {getCurrentInstance} from 'vue';详细可以参考Demo项目中的 page_vue3____composition_api.vue

//RecordApp.UniNativeUtsPlugin={ nativePlugin:true };  //App中启用配套的原生录音插件支持,配置后会使用原生插件进行录音,没有原生插件时依旧使用renderjs H5录音
//App中提升后台录音的稳定性:配置了原生插件后,可配置 `RecordApp.UniWithoutAppRenderjs=true` 禁用renderjs层音频编码(WebWorker加速),变成逻辑层中直接编码(但会降低逻辑层性能),后台运行时可避免部分手机WebView运行受限的影响
//App中提升后台录音的稳定性:需要启用后台录音保活服务(iOS不需要,参考录音权限配置),Android 9开始,锁屏或进入后台一段时间后App可能会被禁止访问麦克风导致录音静音、无法录音(renderjs中H5录音也受影响),请调用配套原生插件的`androidNotifyService`接口,或使用第三方保活插件
    //鸿蒙App中目前暂未提供配套的原生录音插件,需要判断鸿蒙环境下不要禁用renderjs

export default {
data() { return {} }

,mounted() {
    this.isMounted=true;
    //页面onShow时【必须调用】的函数,传入当前组件this
    RecordApp.UniPageOnShow(this); //vue3 setup里用 vue3This 变量
}
,onShow(){ //onShow可能比mounted先执行,页面可能还未准备好
    if(this.isMounted) RecordApp.UniPageOnShow(this);
}

,methods:{
    //请求录音权限
    recReq(){
        //编译成App时提供的授权许可(编译成H5、小程序为免费授权可不填写);如果未填写授权许可,将会在App打开后第一次调用请求录音权限时,弹出“未获得商用授权时,App上仅供测试”提示框
        //RecordApp.UniAppUseLicense='我已获得UniAppID=*****的商用授权';
        
        //RecordApp.RequestPermission_H5OpenSet={ audioTrackSet:{ noiseSuppression:true,echoCancellation:true,autoGainControl:true } }; //这个是Start中的audioTrackSet配置,在h5(H5、App+renderjs)中必须提前配置,因为h5中RequestPermission会直接打开录音
        
        RecordApp.UniWebViewActivate(this); //App环境下必须先切换成当前页面WebView,vue3 setup里用 vue3This 变量
        RecordApp.RequestPermission(()=>{
            console.log("已获得录音权限,可以开始录音了");
        },(msg,isUserNotAllow)=>{
            if(isUserNotAllow){//用户拒绝了录音权限
                //这里你应当编写代码进行引导用户给录音权限,不同平台分别进行编写
            }
            console.error("请求录音权限失败:"+msg);
        });
    }
    
    //开始录音
    ,recStart(){
        //Android App如果要后台录音,需要启用后台录音保活服务(iOS不需要),需使用配套原生插件、或使用第三方保活插件;请参考demo中 main_recTest.vue 的 tryStart_androidNotifyService 方法
        //RecordApp.UniNativeUtsPluginCallAsync("androidNotifyService",{ title:"正在录音" ,content:"正在录音中,请勿关闭App运行" }).then(()=>{...}).catch((e)=>{...}) 注意必须RecordApp.RequestPermission得到权限后调用

        var prevChunk=null; //提供一个变量,在onProcess中实时提取得到pcm数据,注意开始新的转换时需要重置为null
        //录音配置信息
        var set={
            type:"mp3",sampleRate:16000,bitRate:16 //mp3格式,指定采样率hz、比特率kbps,其他参数使用默认配置;注意:是数字的参数必须提供数字,不要用字符串;需要使用的type类型,需提前把格式支持文件加载进来,比如使用wav格式需要提前加载wav.js编码引擎
            /*,audioTrackSet:{ //可选,如果需要同时播放声音(比如语音通话),需要打开回声消除(并不一定会生效;打开后声音可能会从听筒播放,部分环境下(如小程序、App原生插件)可调用接口切换成扬声器外放)
                //注意:H5、App+renderjs中需要在请求录音权限前进行相同配置RecordApp.RequestPermission_H5OpenSet后此配置才会生效
                echoCancellation:true,noiseSuppression:true,autoGainControl:true} */
            ,onProcess:(buffers,powerLevel,duration,sampleRate,newBufferIdx,asyncEnd)=>{
                //全平台通用:可实时上传(发送)数据,配合Recorder.SampleData方法,将buffers中的新数据连续的转换成pcm上传,或使用mock方法将新数据连续的转码成其他格式上传,可以参考Recorder文档里面的:Demo片段列表 -> 实时转码并上传-通用版;基于本功能可以做到:实时转发数据、实时保存数据、实时语音识别(ASR)等
                prevChunk=Recorder.SampleData(buffers,sampleRate,16000,prevChunk); //buffers的采样率不是固定的,要明确转换成需要的采样率
                var newPCM=prevChunk.data; //这个就是最新的pcm数据Int16Array,可以发送、保存等,参考下面takeoffEncodeChunk配置一样实时写入文件
                var pcmBase64=RecordApp.UniBtoa(newPCM.buffer); //二进制pcm转成base64字符串,就是uni.arrayBufferToBase64(newPCM.buffer)

                //可以同时得到mp3+pcm两种格式:takeoffEncodeChunk实时保存mp3、onProcess实时保存pcm

                //注意:App里面是在renderjs中进行实际的音频格式编码操作,此处的buffers数据是renderjs实时转发过来的,修改此处的buffers数据不会改变renderjs中buffers,所以不会改变生成的音频文件,可在onProcess_renderjs中进行修改操作就没有此问题了;如需清理buffers内存,此处和onProcess_renderjs中均需要进行清理,H5、小程序中无此限制
                //注意:如果你要用只支持在浏览器中使用的Recorder扩展插件,App里面请在renderjs中引入此扩展插件,然后在onProcess_renderjs中调用这个插件;H5可直接在这里进行调用,小程序不支持这类插件;如果调用插件的逻辑比较复杂,建议封装成js文件,这样逻辑层、renderjs中直接import,不需要重复编写
                
                //H5、小程序等可视化图形绘制,直接运行在逻辑层;App里面需要在onProcess_renderjs中进行这些操作
                // #ifdef H5 || MP-WEIXIN
                if(this.waveView) this.waveView.input(buffers[buffers.length-1],powerLevel,sampleRate);
                // #endif
                
                /*实时释放清理内存,用于支持长时间录音;在指定了有效的type时,编码器内部可能还会有其他缓冲,必须同时提供takeoffEncodeChunk才能清理内存,否则type需要提供unknown格式来阻止编码器内部缓冲,App的onProcess_renderjs中需要进行相同操作
                if(this.clearBufferIdx>newBufferIdx){ this.clearBufferIdx=0 } //重新录音了就重置
                for(var i=this.clearBufferIdx||0;i<newBufferIdx;i++) buffers[i]=null;
                this.clearBufferIdx=newBufferIdx; */
            }
            ,onProcess_renderjs:`function(buffers,powerLevel,duration,sampleRate,newBufferIdx,asyncEnd){
                //App中才会回调_renderjs结尾的方法(在WebView视图层中运行),但如果配置了 RecordApp.UniWithoutAppRenderjs=true 将不会回调(后台运行更稳定 不受WebView暂停影响)
                //App中在这里修改buffers会改变生成的音频文件,但注意:buffers会先转发到逻辑层onProcess后才会调用本方法,因此在逻辑层的onProcess中需要重新修改一遍
                //本方法可以返回true,renderjs中的onProcess将开启异步模式,处理完后调用asyncEnd结束异步,注意:这里异步修改的buffers一样的不会在逻辑层的onProcess中生效
                //App中是在renderjs中进行的可视化图形绘制,因此需要写在这里,this是renderjs模块的this(也可以用This变量);如果代码比较复杂,请直接在renderjs的methods里面放个方法xxxFunc,这里直接使用this.xxxFunc(args)进行调用
                if(this.waveView) this.waveView.input(buffers[buffers.length-1],powerLevel,sampleRate);
                
                /*和onProcess中一样进行释放清理内存,用于支持长时间录音
                if(this.clearBufferIdx>newBufferIdx){ this.clearBufferIdx=0 } //重新录音了就重置
                for(var i=this.clearBufferIdx||0;i<newBufferIdx;i++) buffers[i]=null;
                this.clearBufferIdx=newBufferIdx; */
            }`
            ,onProcessBefore_renderjs:`function(buffers,powerLevel,duration,sampleRate,newBufferIdx){
                //App中本方法会在逻辑层onProcess之前调用,因此修改的buffers会转发给逻辑层onProcess,本方法没有asyncEnd参数不支持异步处理
                //一般无需提供本方法只用onProcess_renderjs就行,renderjs的onProcess内部调用过程:onProcessBefore_renderjs -> 转发给逻辑层onProcess -> onProcess_renderjs
            }`

            ,takeoffEncodeChunk:true?null:(chunkBytes)=>{
                //全平台通用:实时接收到编码器编码出来的音频片段数据,chunkBytes是Uint8Array二进制数据,可以实时上传(发送)出去
                //App中如果未配置RecordApp.UniWithoutAppRenderjs时,建议提供此回调,因为录音结束后会将整个录音文件从renderjs传回逻辑层,由于uni-app的逻辑层和renderjs层数据交互性能实在太拉跨了,大点的文件传输会比较慢,提供此回调后可避免Stop时产生超大数据回传
                
                //App中使用原生插件时,可方便的将数据实时保存到同一文件,第一帧时append:false新建文件,后面的append:true追加到文件
                //参考demo中的 test_native_plugin.vue 的 realtimeWritePcm2Wav 方法,有用到实时写入pcm数据,并在结束时在文件开头写入wav头,即可生成wav文件,mp3没有文件头直接append即可
                //RecordApp.UniNativeUtsPluginCallAsync("writeFile",{path:"xxx.mp3",append:回调次数!=1, dataBase64:RecordApp.UniBtoa(chunkBytes.buffer)}).then(...).catch(...)
            }
            ,takeoffEncodeChunk_renderjs:true?null:`function(chunkBytes){
                //App中这里可以做一些仅在renderjs中才生效的事情,不提供也行,this是renderjs模块的this(也可以用This变量)
            }`
            
            ,start_renderjs:`function(){
                //App中可以放一个函数,在Start成功时renderjs中会先调用这里的代码,this是renderjs模块的this(也可以用This变量)
                //放一些仅在renderjs中才生效的事情,比如初始化,不提供也行
            }`
            ,stop_renderjs:`function(arrayBuffer,duration,mime){
                //App中可以放一个函数,在Stop成功时renderjs中会先调用这里的代码,this是renderjs模块的this(也可以用This变量)
                //放一些仅在renderjs中才生效的事情,不提供也行
            }`
        };
        
        RecordApp.UniWebViewActivate(this); //App环境下必须先切换成当前页面WebView,vue3 setup里用 vue3This 变量
        RecordApp.Start(set,()=>{
            console.log("已开始录音");
            //【稳如老狗WDT】可选的,监控是否在正常录音有onProcess回调,如果长时间没有回调就代表录音不正常
            //var wdt=this.watchDogTimer=setInterval ... 请参考示例Demo的main_recTest.vue中的watchDogTimer实现
            
            //创建音频可视化图形绘制,App环境下是在renderjs中绘制,H5、小程序等是在逻辑层中绘制,因此需要提供两段相同的代码
            //view里面放一个canvas,canvas需要指定宽高(下面style里指定了300*100)
            //<canvas type="2d" class="recwave-WaveView" style="width:300px;height:100px"></canvas>
            RecordApp.UniFindCanvas(this,[".recwave-WaveView"],`
                this.waveView=Recorder.WaveView({compatibleCanvas:canvas1, width:300, height:100});
            `,(canvas1)=>{
                this.waveView=Recorder.WaveView({compatibleCanvas:canvas1, width:300, height:100});
            });
        },(msg)=>{
            console.error("开始录音失败:"+msg);
        });
    }
    
    //暂停录音
    ,recPause(){
        if(RecordApp.GetCurrentRecOrNull()){
            RecordApp.Pause();
            console.log("已暂停");
        }
    }
    //继续录音
    ,recResume(){
        if(RecordApp.GetCurrentRecOrNull()){
            RecordApp.Resume();
            console.log("继续录音中...");
        }
    }
    
    //停止录音
    ,recStop(){
        //RecordApp.UniNativeUtsPluginCallAsync("androidNotifyService",{ close:true }) //关闭Android App后台录音保活服务
        
        RecordApp.Stop((arrayBuffer,duration,mime)=>{
            //全平台通用:arrayBuffer是音频文件二进制数据,可以保存成文件或者发送给服务器
            //App中如果在Start参数中提供了stop_renderjs,renderjs中的函数会比这个函数先执行
            
            //注意:当Start时提供了takeoffEncodeChunk后,你需要自行实时保存录音文件数据,因此Stop时返回的arrayBuffer的长度将为0字节
            
            //如果是H5环境,也可以直接构造成Blob/File文件对象,和Recorder使用一致
            // #ifdef H5
                var blob=new Blob([arrayBuffer],{type:mime});
                console.log(blob, (window.URL||webkitURL).createObjectURL(blob));
                var file=new File([arrayBuffer],"recorder.mp3");
                //uni.uploadFile({file:file, ...}) //参考demo中的test_upload_saveFile.vue
            // #endif
            
            //如果是App、小程序环境,可以直接保存到本地文件,然后调用相关网络接口上传
            // #ifdef APP || MP-WEIXIN
                RecordApp.UniSaveLocalFile("recorder.mp3",arrayBuffer,(savePath)=>{
                    console.log(savePath); //app保存的文件夹为`plus.io.PUBLIC_DOWNLOADS` 鸿蒙为`Context.filesDir/RecUniSaveFiles`,小程序为 `wx.env.USER_DATA_PATH` 路径
                    //uni.uploadFile({filePath:savePath, ...}) //参考demo中的test_upload_saveFile.vue
                },(errMsg)=>{ console.error(errMsg) });
            // #endif
        },(msg)=>{
            console.error("结束录音失败:"+msg);
        });
    }
    
}
}

⠀

⠀

⠀

⠀

录音权限配置、需要注意的细节

编译成App时录音和权限

编译成App录音时,需要在uni-app项目的 manifest.json 中配置好Android、iOS、鸿蒙的录音权限声明:

text
//Android需要添加的权限,第二个也必须添加
<uses-permission android:name="android.permission.RECORD_AUDIO"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
【注意】Android如果需要在后台录音,需要启用后台录音保活服务,Android 9开始,锁屏或进入后台一段时间后App可能会被禁止访问麦克风导致录音静音、无法录音(renderjs中H5录音、原生插件录音均受影响),请调用配套原生插件的`androidNotifyService`接口,或使用第三方保活插件;同时配置RecordApp.UniWithoutAppRenderjs=true禁用renderjs避免部分手机WebView后台运行受限

//iOS需要声明的权限
NSMicrophoneUsageDescription
【注意】iOS需要在 `App常用其它设置`->`后台运行能力`中提供`audio`配置,不然App切到后台后立马会停止录音

//鸿蒙需要声明的权限
鸿蒙App的编译运行,需要先下载 https://ext.dcloud.net.cn/plugin?name=Recorder-UniHarmony 组件放到uni_modules内
鸿蒙录音权限声明在 @/uni_modules/Recorder-UniHarmony/utssdk/app-harmony/module.json5 内,对应权限声明为“正在申请使用麦克风权限,以便为您提供相应的服务”,可阅读此json5文件自行修改

App中录音时分两种情况:

  1. 默认未配置RecordApp.UniNativeUtsPlugin(未使用原生录音插件和uts插件)时,会在renderjs中使用Recorder H5进行录音,录音数据会实时回传到逻辑层。
  2. 配置了RecordApp.UniNativeUtsPlugin使用原生录音插件或uts插件时,会直接调用原生插件进行录音;录音数据默认会传递到renderjs中进行音频编码处理(WebWorker加速),然后再实时回传到逻辑层,如果配置了RecordApp.UniWithoutAppRenderjs=true时,音频编码处理将会在逻辑层中直接处理。

当App是在renderjs中使用H5进行录音时(未使用原生录音插件和uts插件),iOS上只支持14.3以上版本,且iOS上每次进入页面后第一次请求录音权限时、或长时间无操作再请求录音权限时WebView均会弹出录音权限对话框,不同旧iOS版本(低于iOS17)下H5录音可能存在的问题在App中同样会存在;使用配套的原生录音插件或uts插件时无以上问题和版本限制(uts插件开发中暂不可用),Android也无以上问题。

当App中音频编码是在renderjs中进行处理时,录音结束后会将整个录音文件传回逻辑层,由于uni-app的逻辑层和renderjs层大点的文件传输会比较慢,建议Start时使用takeoffEncodeChunk实时获取音频文件数据可避免Stop时产生超大数据回传;配置了RecordApp.UniWithoutAppRenderjs=true后,因为音频编码直接是在逻辑层中进行,将不存在传输性能损耗,但会影响逻辑层的性能(正常情况轻微不明显),需要配套使用原生录音插件才可以进行此项配置(暂无鸿蒙原生)。

⠀

编译成微信小程序时录音和权限

编译成微信小程序时,录音功能由小程序的RecorderManager提供,屏蔽了微信原有的底层细节(无录音时长限制)。

小程序录音需要用户授予录音权限,调用RecordApp.RequestPermission的时候会检查是否能正常录音,如果用户拒绝了录音权限,会进入错误回调,回调里面你应当编写代码检查wx.getSetting中的scope.record录音权限,然后引导用户进行授权(可调用wx.openSetting打开设置页面,方便用户给权限)。

注意:上架小程序需要到小程序管理后台《用户隐私保护指引》中声明录音权限,否则正式版将无法调用录音功能(请求权限时会直接走错误回调)。

更多细节请参考 miniProgram-wx 测试项目文档。

⠀

编译成H5时录音和权限

编译成H5时,录音功能由Recorder H5提供,无需额外处理录音权限。

⠀

PCM音频流式播放、语音通话、回声消除、声音外放

在App、H5中,均可使用H5版的BufferStreamPlayer来实时流式播放语音;其中App中需要在renderjs中加载BufferStreamPlayer,在逻辑层中调用RecordApp.UniWebViewVueCall等方法将逻辑层中接收到的实时语音数据发送到renderjs中播放;播放声音的同时进行录音,声音可能会被录进去产生回声,因此一般需要打开回声消除;调用代码参考demo中的test_realtime_voice.vue。

App中如果搭配使用了配套的原生录音插件,可以调用原生实现的PcmPlayer播放器实时流式播放PCM音频,边录音边播放更流畅;同时也支持完整播放,比如AI语音合成的播放;调用代码参考demo中的test_player_nativePlugin_pcmPlayer.vue。

微信小程序请参考 miniProgram-wx 文档里面的同名章节,使用WebAudioContext播放。

配置audioTrackSet可尝试打开回声消除,或者切换听筒播放或外放,打开回声消除时,一般会转为听筒播放显著降低回声。

js
//打开回声消除
RecordApp.Start({
    ... 更多配置参数请参考RecordApp文档
    //此配置App、H5、小程序均可打开回声消除;注意:H5、App+renderjs中需要在请求录音权限前进行相同配置RecordApp.RequestPermission_H5OpenSet后此配置才会生效
    ,audioTrackSet:{echoCancellation:true,noiseSuppression:true,autoGainControl:true}
    
    //Android指定麦克风源(App搭配原生插件、小程序可用),0 DEFAULT 默认音频源,1 MIC 主麦克风,5 CAMCORDER 相机方向的麦,6 VOICE_RECOGNITION 语音识别,7 VOICE_COMMUNICATION 语音通信(带回声消除)
    ,android_audioSource:7 //不建议使用,提供此配置时优先级比audioTrackSet更高,默认值为0
    
    //iOS的AVAudioSession setCategory的withOptions参数值(App搭配原生插件可用),取值请参考配套原生插件文档中的iosSetDefault_categoryOptions
    //,ios_categoryOptions:0x1|0x4 //不建议使用,默认值为5(0x1|0x4)
});

//App搭配原生插件时尝试切换听筒播放或外放
await RecordApp.UniNativeUtsPluginCallAsync("setSpeakerOff",{off:true或false});
//小程序尝试切换
wx.setInnerAudioOption({ speakerOn:false或true })
//H5不支持切换

⠀

⠀

⠀

详细文档、RecordApp方法、属性文档

请先阅读 demo_UniApp文档,含Demo项目;更高级使用还需深入阅读 Recorder文档、RecordApp文档 (均为完整的一个README.md文档),Recorder文档中包含了更丰富的示例代码:基础录音、实时处理、格式转码、音频分析、音频混音、音频生成 等等,大部分能在uniapp中直接使用。

⠀

⠀

⠀

本组件的授权许可限制

本组件内的app-uni-support.js文件在uni-app中编译到App平台时仅供测试用(App平台包括:Android App、iOS App、鸿蒙App),不可用于正式发布或商用,正式发布或商用需先到DCloud插件市场购买此带授权的插件(费用为¥199元,赠送Android版原生插件),即可获得授权许可;插件市场支持开票,具体请参考 DCloud 如何开具发票 ;编译到其他平台时无此授权限制,比如:H5、小程序,均为免费授权。

在App中,如果未获得授权许可,将会在App打开后第一次调用RecordApp.RequestPermission请求录音权限时,弹出“未获得商用授权时,App上仅供测试”提示框。

在DCloud插件市场购买了带授权的插件获得了授权后,请在调用RecordApp.RequestPermission请求录音权限前,赋值RecordApp.UniAppUseLicense="我已获得UniAppID=***的商用授权"(星号为你项目的uni-app应用标识),就不会弹提示框了;或者直接使用配套的原生录音插件,设置RecordApp.UniNativeUtsPlugin参数后,也不会弹提示框;其他情况请联系作者咨询,更多细节请参考本组件的GitHub文档。

获取授权、需要技术支持、或有不清楚的地方,可以联系客服:本插件页面进入交流群,或QQ( 125 165 4593 ) ,或者直接联系作者QQ( 753 610 399 ) (回复可能没有客服及时)。

插件开发维护不易,感谢支持~

⠀

⠀


File: app-support-sample/demo_UniApp/uni_modules/Recorder-UniCore/changelog.md

[Binary file]


File: app-support-sample/demo_UniApp/uni_modules/Recorder-UniCore/license.md

[Binary file]


File: app-support-sample/demo_UniApp/uni_modules/Recorder-UniCore/package.json

{
"id": "Recorder-UniCore",
"displayName": "跨平台Recorder录音插件:支持多种格式、音频可视化、实时上传、语音识别",
"version": "1.0.260708",
"description": "支持H5、Android iOS 鸿蒙App、微信小程序;mp3 wav pcm g711a g711u ogg amr 录音格式;实时帧回调处理 音频转码 波形动画 ASR语音转文字 无录制时长限制",
"keywords": [
"Recorder-UniCore",
"recorder-core",
"RecordApp",
"record",
"recording"
],
"repository": "https://github.com/xiangyuecn/Recorder",
"engines": {
"HBuilderX": "^3.6.11",
"uni-app": "^3.7.13",
"uni-app-x": ""
},
"dcloudext": {
"type": "component-vue",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": "753610399"
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "录音权限"
},
"npmurl": "",
"darkmode": "√",
"i18n": "√",
"widescreen": "√"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "x",
"aliyun": "x",
"alipay": "x"
},
"client": {
"uni-app": {
"vue": {
"vue2": "√",
"vue3": "√"
},
"web": {
"safari": "√",
"chrome": "√"
},
"app": {
"vue": "√",
"nvue": "√",
"android": "√",
"ios": "√",
"harmony": "√"
},
"mp": {
"weixin": "√",
"alipay": "x",
"toutiao": "x",
"baidu": "x",
"kuaishou": "x",
"jd": "x",
"harmony": "-",
"qq": "x",
"lark": "x",
"xhs": "x"
},
"quickapp": {
"huawei": "x",
"union": "x"
}
},
"uni-app-x": {
"web": {
"safari": "-",
"chrome": "-"
},
"app": {
"android": "-",
"ios": "-",
"harmony": "-"
},
"mp": {
"weixin": "-"
}
}
}
}
}
}


File: app-support-sample/demo_UniApp/uni_modules/Recorder-UniHarmony/readme.md

使用方法文档

本UTS插件为跨平台Recorder录音插件Recorder-UniCore的配套组件,提供鸿蒙App下的权限请求、文件保存原生功能。

插件默认配置了 麦克风 权限,对应权限声明为 正在申请使用麦克风权限,以便为您提供相应的服务,也可用于更多权限的声明和请求;请参考下面的权限请求方法,可以到json5配置文件中自行添加修改。

import

js
import { Harmony_RequestPermissionAsync, Harmony_SaveLocalFile } from '@/uni_modules/Recorder-UniHarmony';

Harmony_RequestPermissionAsync(permissions : Array)

请求权限,参数有几个权限,异步返回的结果对应的就有几个值。非鸿蒙app调用会直接抛异常。

js
var res=await Harmony_RequestPermissionAsync(["ohos.permission.MICROPHONE","ohos.permission.CAMERA"]);
//res=[1,3] 返回结果:1 有权限、3 无权限、2 (未用到)

// 权限需要先到 @/uni_modules/Recorder-UniHarmony/utssdk/app-harmony/module.json5 内声明,可阅读此json5配置文件自行添加修改
// 权限字符串参考 https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/permissions-for-all-user

Harmony_SaveLocalFile(fileName : string, base64 : string)

保存base64任意二进制数据到此文件名下,返回保存的完整路径。非鸿蒙app调用会直接抛异常。

js
var path=Harmony_SaveLocalFile("test.txt", "dGVzdDEyMw==");
//path=Context.filesDir+"/RecUniSaveFiles"+"/test.txt" 返回保存的完整路径

File: app-support-sample/demo_UniApp/uni_modules/Recorder-UniHarmony/changelog.md

1.0.260427(2026-04-27)

提交发布


File: app-support-sample/demo_UniApp/uni_modules/Recorder-UniHarmony/package.json

{
"id": "Recorder-UniHarmony",
"displayName": "鸿蒙App权限请求+Base64二进制文件保存",
"version": "1.0.260427",
"description": "鸿蒙App环境下专用,为跨平台Recorder录音插件Recorder-UniCore的配套组件,可以单独拿出来用和学习",
"keywords": [
"permission",
"saveFile"
],
"repository": "https://github.com/xiangyuecn/Recorder",
"engines": {
"HBuilderX": "^3.6.8",
"uni-app": "^3.7.6",
"uni-app-x": "^3.7.6"
},
"dcloudext": {
"type": "uts",
"sale": {
"regular": {
"price": "0.00"
},
"sourcecode": {
"price": "0.00"
}
},
"contact": {
"qq": "753610399"
},
"declaration": {
"ads": "无",
"data": "插件不采集任何数据",
"permissions": "无"
},
"npmurl": "",
"darkmode": "√",
"i18n": "√",
"widescreen": "√"
},
"uni_modules": {
"dependencies": [],
"encrypt": [],
"platforms": {
"cloud": {
"tcb": "x",
"aliyun": "x",
"alipay": "x"
},
"client": {
"uni-app": {
"vue": {
"vue2": {
"extVersion": "1.0.260427",
"minVersion": ""
},
"vue3": {
"extVersion": "1.0.260427",
"minVersion": ""
}
},
"web": {
"safari": "x",
"chrome": "x"
},
"app": {
"vue": {
"extVersion": "1.0.260427",
"minVersion": ""
},
"nvue": {
"extVersion": "1.0.260427",
"minVersion": ""
},
"android": "x",
"ios": "x",
"harmony": {
"extVersion": "",
"minVersion": "13"
}
},
"mp": {
"weixin": "x",
"alipay": "x",
"toutiao": "x",
"baidu": "x",
"kuaishou": "x",
"jd": "x",
"harmony": "x",
"qq": "x",
"lark": "x",
"xhs": "x"
},
"quickapp": {
"huawei": "x",
"union": "x"
}
},
"uni-app-x": {
"web": {
"safari": "x",
"chrome": "x"
},
"app": {
"android": "x",
"ios": "x",
"harmony": {
"extVersion": "",
"minVersion": "13"
}
},
"mp": {
"weixin": "x"
}
}
}
}
}
}


File: app-support-sample/demo_UniApp/uni_modules/Recorder-UniHarmony/utssdk/app-harmony/resources/base/element/string.json

{
"string": [
{
"name": "microphone_reason__recorder_uniharmony",
"value": "正在申请使用麦克风权限,以便为您提供相应的服务"
}
]
}


File: app-support-sample/miniProgram-wx/README.md

[Binary file]


File: app-support-sample/miniProgram-wx/app.json

{
"pages": [
"pages/recTest/recTest",
"pages/test_asr/test_asr"
],
"window": {
"backgroundTextStyle": "light",
"navigationBarBackgroundColor": "#fff",
"navigationBarTitleText": "Weixin",
"navigationBarTextStyle": "black"
},
"style": "v2",
"sitemapLocation": "sitemap.json",
"lazyCodeLoading": "requiredComponents"
}


File: app-support-sample/miniProgram-wx/project.config.json

{
"appid": "youAppID",
"compileType": "miniprogram",
"libVersion": "2.32.3",
"packOptions": {
"ignore": [],
"include": []
},
"setting": {
"urlCheck": false,
"coverView": true,
"es6": true,
"postcss": true,
"minified": true,
"enhance": true,
"showShadowRootInWxmlPanel": true,
"packNpmRelationList": [],
"babelSetting": {
"ignore": [],
"disablePlugins": [],
"outputPath": ""
}
},
"condition": {},
"editorSetting": {
"tabIndent": "tab",
"tabSize": 4
},
"projectname": "miniProgram-wx"
}


File: app-support-sample/miniProgram-wx/sitemap.json

{
"desc": "关于本文件的更多信息,请参考文档 https://developers.weixin.qq.com/miniprogram/dev/framework/sitemap.html",
"rules": [{
"action": "allow",
"page": "*"
}]
}


File: app-support-sample/miniProgram-wx/copy-rec-src/请复制根目录的src文件夹到这里.md

npm支持太差,手动copy Recorder根目录下的src文件夹到本目录即可

比如可用路径:/copy-rec-src/src/recorder-core.js

自己项目实际使用时,可按需copy仅你require引用到了的js文件到小程序项目中,免得小程序源码过大(一般编译时会忽略掉未引用到的js文件)。

微信开发者工具对npm支持太差,“构建 npm”功能没什么卵用,npm包内没被main文件引用的js会被全部丢弃,导致文件缺失;似乎也不允许手动require含node_modules的路径;因此请直接copy根目录内的src文件夹到本小程序源码的copy-rec-src文件夹内,直接当做小程序源码调用,好使。


File: app-support-sample/miniProgram-wx/pages/recTest/recTest.json

{
"navigationBarTitleText":"RecordApp录音测试"
,"usingComponents": {
"test-player":"../test_player__/test_player__"
,"test-upload-view":"../test_upload_saveFile__/test_upload_saveFile__"
,"test-rtvoice-view":"../test_realtime_voice__/test_realtime_voice__"
}
}


File: app-support-sample/miniProgram-wx/pages/test_asr/test_asr.json

{
"navigationBarTitleText":"RecordApp语音识别ASR"
,"usingComponents": {
"test-player":"../test_player__/test_player__"
}
}


File: app-support-sample/miniProgram-wx/pages/test_player__/test_player__.json

{
"component": true,
"usingComponents": {}
}


File: app-support-sample/miniProgram-wx/pages/test_realtime_voice__/test_realtime_voice__.json

{
"component": true,
"usingComponents": {}
}


File: app-support-sample/miniProgram-wx/pages/test_upload_saveFile__/test_upload_saveFile__.json

{
"component": true,
"usingComponents": {}
}


File: assets/demo-asr/README.md

[Binary file]


File: assets/demo-ts/package.json

{
"name": "recorder-ts-demo",
"version": "1.0.0",
"scripts": {
"build": "webpack --progress --mode production --config package.webpack.build.js",
"build-dev": "webpack --progress --mode development --config package.webpack.build.js"
},
"dependencies": {
"recorder-core": "*",
"webpack": "4.41.2",
"webpack-cli": "3.3.10",
"typescript": "4.7.4",
"ts-loader": "8.4.0"
}
}


File: assets/demo-ts/tsconfig.json

{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */

text
/* Projects */
// "incremental": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo",              /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true,                 /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true,             /* Reduce the number of projects loaded automatically by TypeScript. */

/* Language and Environment */
"target": "es5",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve",                                /* Specify what JSX code is generated. */
// "experimentalDecorators": true,                   /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true,                    /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true,                                    /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true,                  /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto",                        /* Control what method is used to detect module-format JS files. */

/* Modules */
"module": "commonjs",                                /* Specify what module code is generated. */
// "rootDir": "./",                                  /* Specify the root folder within your source files. */
// "moduleResolution": "node",                       /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./",                                  /* Specify the base directory to resolve non-relative module names. */
// "paths": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [],                                      /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true,                     /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [],                             /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true,                        /* Enable importing .json files. */
// "noResolve": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */

/* JavaScript Support */
// "allowJs": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true,                                  /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */

/* Emit */
// "declaration": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true,                           /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true,                      /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true,                                /* Create source map files for emitted JavaScript files. */
// "outFile": "./",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "./dist",                                   /* Specify an output folder for all emitted files. */
// "removeComments": true,                           /* Disable emitting comments. */
// "noEmit": true,                                   /* Disable emitting files from a compilation. */
// "importHelpers": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove",               /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "",                                 /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "",                                    /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true,                          /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf",                                /* Set the newline character for emitting files. */
// "stripInternal": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true,                            /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true,                       /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./",                           /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */

/* Interop Constraints */
// "isolatedModules": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true,             /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true,            /* Ensure that casing is correct in imports. */

/* Type Checking */
"strict": true,                                      /* Enable all strict type-checking options. */
// "noImplicitAny": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true,                         /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true,             /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true,                           /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true,                             /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true,                           /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true,                       /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true,               /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true,                 /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true,                        /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true,                     /* Disable error reporting for unreachable code. */

/* Completeness */
// "skipDefaultLibCheck": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true                                 /* Skip type checking all .d.ts files. */

}
}


File: assets/demo-vue/README.md

vue+webpack测试

支持Vue2、Vue3,自己编写代码可按照仓库首页的README.md正常import Recorder from 'recorder-core'使用就行,另外专门写了一篇文章《vue3实现H5网页录音并上传(mp3、wav)兼容Android、iOS和PC端》方便参考。

运行方法

【1】编译vue源码

text
npm install
npm run build-dev

【2】浏览器访问

然后就可以打开index.html查看集成Recorder的效果,打开recordapp.html查看集成RecordApp的效果。


File: assets/demo-vue/package.json

{
"name": "recorder-vue-demo",
"version": "1.0.0",
"scripts": {
"build": "webpack --progress --mode production --config package.webpack.build.js"
,"build-dev": "webpack --progress --mode development --config package.webpack.build.js"
},
"dependencies": {
"recorder-core": "*"

text
,"vue": "2.6.10"
,"webpack": "4.41.2"
,"webpack-cli": "3.3.10"

,"vue-loader": "15.7.2"
,"vue-template-compiler": "2.6.10"
,"style-loader": "1.0.1"
,"css-loader": "3.2.0"
,"babel-core": "6.26.3"
,"babel-loader": "7.1.5"
,"babel-preset-env": "1.7.0"

}
}


File: assets/node-localServer/README.md

[Binary file]


File: assets/node-localServer/package.json

{
"name": "nodejs-localServer-http-websocket",
"version": "1.0.0",
"scripts": {
"start": "node main.js"
},
"dependencies": {
"recorder-core": "",
"nodejs-websocket": "
",
"parse-multipart-data": "*"
}
}


File: assets/npm-home/README.md

Recorder:recorder-core 用于html5录音

GitHub: https://github.com/xiangyuecn/Recorder

Gitee: https://gitee.com/xiangyuecn/Recorder

文档和详细使用方法请参考上面两个Recorder仓库。npm recorder这个名字已被使用,因此在Recorder基础上增加后缀-core,就命名为recorder-core,和Recorder核心文件同名。

如何使用

使用npm安装

text
npm install recorder-core

引入Recorder库

@@Ref RecordApp.README.ImportCode@@

Recorder调用录音

@@Ref README.Codes@@

RecordApp调用录音

@@Ref RecordApp.README.Codes@@

@@Remove Start@@

作者自用:本npm包如何编写提交

  1. 运行根目录/src:npm start,进行文件copy
  2. 进入assets/npm-home/npm-files目录,进行提交
text
//登录,注意一定要使用npmjs的源
npm login

//发布包
注意到npm-files目录执行!!!!!  npm publish
	下载发布的文件 https://registry.npmjs.org/recorder-core/-/recorder-core-1.0.00000000.tgz

//查询当前配置的源
npm get registry
//设置成淘宝源
npm config set registry https://registry.npmmirror.com/
//换成原来的源
npm config set registry https://registry.npmjs.org/

@@Remove End@@


File: assets/npm-home/hash-history.txt

[
{
"sha1": "5f69a70997681eedcb96d991b9699c49519899aa",
"time": "2026/7/8 23:13:19"
},
{
"sha1": "60e31f2c5cee98889d9b9bf1d580e09e231cff41",
"time": "2025/1/11 11:31:38"
},
{
"sha1": "f2e8d54599f772c025d93ae8b34b55df5b96195a",
"time": "2024/10/20 22:19:50"
},
{
"sha1": "43202e2f7ab0970bfde542adf266889002eecd83",
"time": "2024/10/20 20:21:45"
},
{
"sha1": "578ca0a60fca9043bb52703b20162b8cd2a29e0d",
"time": "2024-4-9 21:51:52"
}
]


File: assets/npm-home/package.json

{
"name": "recorder-core",
"version": "1.3.123456.9999",
"description": "Recorder库: html5 js 录音 mp3 wav ogg webm amr g711a g711u 格式,支持pc和Android、iOS部分浏览器、Hybrid App(提供Android iOS App源码)、微信,提供ASR语音识别转文字 H5版语音通话聊天示例 DTMF编码解码",
"homepage": "https://github.com/xiangyuecn/Recorder",
"main": "src/recorder-core.js",
"keywords": [
"recorder",
"recordapp",
"record",
"html录音",
"h5录音",
"html5",
"mp3",
"wav",
"ASR",
"语音识别",
"语音转文字",
"DTMF",
"recording",
"webrtc"
],
"repository": {
"type": "git",
"url": "https://github.com/xiangyuecn/Recorder.git"
},
"bugs": {
"url": "https://github.com/xiangyuecn/Recorder/issues"
},
"author": "xiangyuecn",
"license": "MIT"
}


File: assets/sonic-java/README.md

Sonic Java版测试

Sonic是一个音频变速变调的开源库,waywardgeek/sonic

Sonic.java已移植为sonic.js,并且做了精简,代码在extensions/sonic.js

测试方法

  1. 录制一个test.wav放到本目录
  2. 先安装好jdk,用于编译和运行java文件
  3. 运行start.bat即可控制和播放test.wav

File: src/package.json

{
"name": "build",
"version": "1.0.0",
"scripts": {
"start": "node package-build.js"
},
"dependencies": {
"uglify-js": "3.3.16"
}
}