# Technical Documentation: xiangyuecn/Recorder

> ℹ️ **Provenance:** Multi-Tier Ingestion · [CodeWiki Reference](https://codewiki.google/github.com/xiangyuecn/Recorder)

### Overview (`README.md`)

**【[源GitHub仓库](https://github.com/xiangyuecn/Recorder)】 | 【[Gitee镜像库](https://gitee.com/xiangyuecn/Recorder)】本文档中默认使用[github.io部署的链接](https://xiangyuecn.github.io/Recorder/)，如果无法访问，请直接下载仓库源码到本地然后双击打开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](./app-support-sample)可在微信小程序、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在线测试](https://xiangyuecn.github.io/Recorder/) ]
[ [H5 QuickStart](https://xiangyuecn.github.io/Recorder/QuickStart.html) ]
[ [H5 vue](https://xiangyuecn.github.io/Recorder/assets/demo-vue/) ]
[ [H5 ts](https://xiangyuecn.github.io/Recorder/assets/demo-ts/) ]
[ [H5 es module](https://xiangyuecn.github.io/Recorder/assets/ztest_es_module.html) ]
[ [旧版本测试](https://xiangyuecn.github.io/Recorder/assets/工具-GitHub页面历史版本访问.html#url=xiangyuecn:Recorder@1.0.19120600,/) ]


**Recorder App** ：
[ [RecordApp测试](https://xiangyuecn.github.io/Recorder/app-support-sample/) ]
[ [App QuickStart](https://xiangyuecn.github.io/Recorder/app-support-sample/QuickStart.html) ]
[ [App vue](https://xiangyuecn.github.io/Recorder/assets/demo-vue/recordapp.html) ]
[ [Android、iOS App源码](./app-support-sample) ]
[ [微信小程序源码](./app-support-sample/miniProgram-wx) ]
[ [uni-app源码](./app-support-sample/demo_UniApp) ]



**工具集** ：
[ [Recorder代码运行和静态分发](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html) ]
[ [PCM转WAV播放测试和转码](https://xiangyuecn.github.io/Recorder/assets/工具-裸PCM转WAV播放测试.html) ]
[ [无用户操作测试](https://xiangyuecn.github.io/Recorder/assets/ztest_no_user_operation.html) ]
[ [Can I Use查看浏览器支持情况](https://caniuse.com/#search=getUserMedia) ]



> [<img src="https://gitee.com/xiangyuecn/Recorder/raw/master/assets/demo.png" width="100px">](https://xiangyuecn.github.io/Recorder/) 手机浏览器扫一扫在线测试



[​](?)

### Demo片段列表

1. [【教程】【PCM基础】buffers转pcm、转采样率、改音量、pcm合并转mp3等格式](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.pcm.basic)
1. [【教程】【音频流】【上传】实时转码上传-实时帧回调版](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.realtime.encode_transfer_mp3)
1. [【教程】【音频流】【上传】实时转码上传-pcm固定帧大小](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.realtime.encode_transfer_frame_pcm)
1. [【教程】【音频流】【上传】实时转码上传-通用转码旧版](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.realtime.encode_transfer)
1. [【教程】【音频流】【播放】实时解码播放音频片段](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.realtime.decode_buffer_stream_player)
1. [【教程】【播放】【可视化】实时录制处理audio、video播放流](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.source_stream.capture_stream)
1. [【教程】【ASR】实时语音识别、音频文件转文字-阿里云版](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.realtime.asr.aliyun.short)
1. [【Demo库】【格式转换】-mp3等格式解码转成其他格式](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.transform.mp32other)
1. [【Demo库】【格式转换】-wav格式转成其他格式](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.transform.wav2other)
1. [【Demo库】【格式转换】-amr格式转成其他格式](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.transform.amr2other)
1. [【Demo库】【文件合并】-mp3多个片段文件合并](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.merge.mp3_merge)
1. [【Demo库】【文件合并】-wav多个片段文件合并](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.merge.wav_merge)
1. [【教程】实时多路音频混音](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.realtime.mix_multiple)
1. [【教程】变速变调音频转换](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.sonic.transform)
1. [【教程】新录音从老录音接续、或录制中途插入音频](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.env_in.follow)
1. [【教程】DTMF（电话拨号按键信号）解码、编码](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.dtmf.decode_and_encode)
1. [【测试】PCM采样率转换测试](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.samplerate.raise)
1. [【Demo库】【信号处理】IIR低通、高通滤波](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=dsp.lib.filter.iir)
1. [【测试】【信号处理】FFT频域分析ECharts频谱曲线图](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=dsp.test.fft.analysis)
1. [【测试】WebM格式解析并提取音频](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=test.webm.extract_audio)
1. [【测试】G711、G72X编码和解码播放](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=test.g7xx.engine)
1. [【Demo库】js二进制转换、基础知识-Base64/Hex/Int16Array/ArrayBuffer/Blob](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.js-binary-bytes)
1. [【测试】音乐合成-用波形函数将歌曲简谱文本转成PCM](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=test.create-audio.nmn2pcm)
1. [【测试】音频可视化相关插件测试](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=test.extensions.visualization)


### App Demo
Android Demo App ：
[下载APK](app-support-sample/demo_android/app-debug.apk.zip)（40kb，删除.zip后缀，
[源码](app-support-sample/demo_android)）

iOS Demo App ：[下载源码](app-support-sample/demo_ios) 自行编译




[​](?)

## 【QQ群】交流与支持

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

<img src="https://gitee.com/xiangyuecn/Recorder/raw/master/assets/qq_group_781036591.png" width="220px">






[​](?)

[​](?)

[​](?)


<p align="center"><a href="https://github.com/xiangyuecn/Recorder"><img width="100" src="https://gitee.com/xiangyuecn/Recorder/raw/master/assets/icon.png" alt="Recorder logo"></a></p>

<p align="center">
  <a title="Stars" href="https://github.com/xiangyuecn/Recorder"><img src="https://img.shields.io/github/stars/xiangyuecn/Recorder?color=0b1&logo=github" alt="Stars"></a>
  <a title="Forks" href="https://github.com/xiangyuecn/Recorder"><img src="https://img.shields.io/github/forks/xiangyuecn/Recorder?color=0b1&logo=github" alt="Forks"></a>
  <a title="npm Downloads" href="https://www.npmjs.com/package/recorder-core"><img src="https://img.shields.io/npm/dt/recorder-core?color=0b1&logo=npm" alt="npm Downloads"></a>
  <a title="npm Version" href="https://www.npmjs.com/package/recorder-core"><img src="https://img.shields.io/npm/v/recorder-core?color=0b1&logo=npm" alt="npm Version"></a>
  <a title="License" href="https://github.com/xiangyuecn/Recorder/blob/master/LICENSE"><img src="https://img.shields.io/github/license/xiangyuecn/Recorder?color=0b1&logo=github" alt="License"></a>
  <a title="JsDelivr CDN" href="https://www.jsdelivr.com/package/gh/xiangyuecn/Recorder"><img src="https://img.shields.io/badge/CDN-JsDelivr-0b1" alt="JsDelivr CDN"></a>
  <a title="unpkg CDN" href="https://unpkg.com/recorder-core/"><img src="https://img.shields.io/badge/CDN-unpkg-0b1" alt="unpkg CDN"></a>
  <a title="51LA" href="https://web.51.la/report/main?comId=20469973"><img src="https://img.shields.io/badge/51LA-available-0b1" alt="51LA"></a>
</p>



[​](?)

# :open_book:快速使用

你可以通过阅读和运行[QuickStart.html](https://xiangyuecn.github.io/Recorder/QuickStart.html)文件来快速入门学习，直接将`QuickStart.html`copy到你的(https、localhost)网站中，无需其他文件，就能正常开始测试了；**注意：需要在https、localhost等[安全环境](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Privacy_and_security)下才能进行录音。**

> 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）](https://xiangyuecn.github.io/ACME-HTML-Web-Browser-Client/ACME-HTML-Web-Browser-Client.html)；不建议自己生成根证书来签发域名证书，一个是流程复杂，每个设备均要导入根证书，致命的是很多现代浏览器不再信任用户目录下导入的根证书（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](https://www.jsdelivr.com/package/gh/xiangyuecn/Recorder)、[unpkg](https://unpkg.com/recorder-core/)）
``` html
<script src="recorder.mp3.min.js"></script> 
```
或者直接使用源码（src内的为源码、dist内的为压缩后的），可以引用src目录中的recorder-core.js+相应类型的实现文件，比如要mp3录音：
``` html
<script src="src/recorder-core.js"></script> 

<script src="src/engine/mp3.js"></script> 
<script src="src/engine/mp3-engine.js"></script> 

<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】调用录音，播放结果
[​](?Ref=Codes&Start)这里假设只录3秒，录完后立即播放，[在线编辑运行此代码>>](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?idf=self_base_demo)。录音结束后得到的是Blob二进制文件对象，可以下载保存成文件、用`FileReader`读取成`ArrayBuffer`或者`Base64`给js处理，或者参考下一节上传示例直接上传。
```
/* Detailed source-code truncated for AI context efficiency. */
```
[​](?RefEnd)


[​](?)

## 【附】录音上传示例
```
/* Detailed source-code truncated for AI context efficiency. */
```


[​](?)

## 【附】js中的二进制基础知识：Uint8Array Int16Array ArrayBuffer Blob/File
更多js二进制数据基础知识请阅读：[js二进制转换-Base64/Hex/Int16Array/ArrayBuffer/Blob](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.js-binary-bytes)
```
/* Detailed source-code truncated for AI context efficiency. */
```





[​](?)

[​](?)

## 【附】Android App - WebView中录音示例
[app-support-sample/demo_android](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"/>
```

2. WebView的WebChromeClient中实现`onPermissionRequest`网页授权请求

如果未实现此方法，**WebView默认会拒绝H5录音权限**；onPermissionRequest中需要先获取App的系统录音权限（Activity里面必须先用this.checkSelfPermission检测权限，否则直接this.requestPermissions会造成WebView触发touchcancel打断长按），然后再grant网页权限，可参考Android Demo中的[MainActivity.java](app-support-sample/demo_android/app/src/main/java/com/github/xianyuecn/recorder/MainActivity.java)中的实现代码。

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

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

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


[​](?)

## 【附】iOS App - WebView中录音示例
[app-support-sample/demo_ios](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接口](https://developer.apple.com/documentation/webkit/wkuidelegate)，可参考iOS Demo中的[MainView.swift](app-support-sample/demo_ios/recorder/MainView.swift)中的代码；如果未实现，会导致H5录音每次打开页面后第一次录音时、或长时间无操作再打开录音时均会弹出录音权限对话框。

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


[​](?)

## 【附】微信小程序中录音集成参考
`RecordApp`支持在微信小程序中直接进行录音，请参考小程序Demo项目：[miniProgram-wx](./app-support-sample/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](./app-support-sample/demo_UniApp)，对应的组件可到[DCloud 插件市场下载](https://ext.dcloud.net.cn/plugin?name=Recorder-UniCore)。

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










[​](?)

[​](?)

[​](?)

[​](?)

[​](?)

[​](?)

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

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


浏览器Audio Media[兼容性](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats#Browser_compatibility)mp3最好，wav还行，其他要么不支持播放，要么不支持编码；因此本库最佳推荐使用mp3、wav格式，代码也是优先照顾这两种格式。

`getUserMedia`的浏览器支持情况和兼容性，可到[Can I use](https://caniuse.com/?search=getUserMedia)查看。


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

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

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

**特别注**：如果在`iframe`里面调用的录音功能，并且和上层的网页是不同的域（跨域了），如果未设置相应策略，权限永远是被拒绝的，[参考此处](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia#Privacy_and_security)。另外如果要在`非跨域的iframe`里面使用，最佳实践应该是让window.top去加载Recorder（异步加载js），iframe里面使用top.Recorder，免得各种莫名其妙。





## 已知问题

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

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

*2024-09-09* [#230](https://github.com/xiangyuecn/Recorder/issues/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](https://xiangyuecn.github.io/Recorder/assets/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`参数为配置对象，默认配置值如下：
```
/* Detailed source-code truncated for AI context efficiency. */
```

**注意：set内是数字的明确传数字**，不要传字符串之类的导致不可预测的异常，其他有配置的地方也是一样（感谢`214282049@qq.com`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方法](assets/node-codes/test-all-type.js)。

`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添加，[点此](https://web.51.la/report/main?comId=20469973)前往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参数，支持的参数请参考[此文档](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaRecorder/MediaRecorder)。*

> 本连接实现原理：通过MediaRecorder对MediaStream进行录制，格式`audio/webm; codecs=pcm`，MediaRecorder会将实时录制的PCM数据（48k+32位）回传给js，因此只需要知道WebM的封装格式就能提取出PCM数据，请参考测试代码：[WebM格式解析并提取音频](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=test.webm.extract_audio)


### 【静态属性】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；此坑已填好，[复现测试页面](https://xiangyuecn.github.io/Recorder/assets/ztest_chrome_bug_AudioWorkletNode.html)。*

**注意：由于AudioWorklet内部[1秒会产生375次回调](https://developer.mozilla.org/en-US/docs/Web/API/AudioWorkletProcessor/process)，在移动端可能会有性能问题导致浏览器回调丢失，进而导致录音数据、时长变短，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原生实现，如需定制可联系作者。

【附】部分插件使用效果图（[在线运行观看](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=test.extensions.visualization)）：




[​](?)

[​](?)

## WaveView插件
[waveview.js](src/extensions/waveview.js)，7kb大小源码，录音时动态显示波形，具体样子参考演示地址页面。此插件参考[MCVoiceWave](https://github.com/HaloMartin/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](src/extensions/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](src/extensions/frequency.histogram.view.js) + [lib.fft.js](src/extensions/lib.fft.js)，12kb大小源码，音频可视化频率直方图显示，具体样子参考演示地址页面。此插件核心算法参考Java开源库[jmp123](https://sourceforge.net/projects/jmp123/files/)的代码编写的，`jmp123`版本`0.3`；直方图特意优化主要显示0-5khz语音部分（线性），其他高频显示区域较小，不适合用来展示音乐频谱，可通过配置fullFreq来恢复成完整的线性频谱，或自行修改源码修改成倍频程频谱（伯德图、对数频谱）；本可视化插件可以移植到其他语言环境，如需定制可联系作者。

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


### 【构造】histogram=Recorder.FrequencyHistogramView(set)
构造函数，`set`参数为配置对象，默认配置值如下：
```
/* Detailed source-code truncated for AI context efficiency. */
```

### 【方法】histogram.input(pcmData,powerLevel,sampleRate)
输入音频数据，更新直方图显示。pcmData `[Int16,...]` 一维数组，为当前的录音数据片段，其他参数和`onProcess`回调相同。




[​](?)

[​](?)

## BufferStreamPlayer插件
[buffer_stream.player.js](src/extensions/buffer_stream.player.js)，31kb大小源码，实时播放录音片段文件，把片段文件转换成MediaStream流，[参考此demo片段在线测试使用](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.realtime.decode_buffer_stream_player)。

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

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

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

### BufferStreamPlayer文档
```
/* Detailed source-code truncated for AI context efficiency. */
```

### 【方法】stream.input(anyData)
输入任意格式的音频数据，未完成start前调用会等到start成功后生效。
```
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](src/extensions/asr.aliyun.short.js)，29kb大小源码，ASR，阿里云语音识别（语音转文字），支持实时语音识别、单个音频文件转文字，[参考此demo片段在线测试使用](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.realtime.asr.aliyun.short)。

- 本插件通过调用 阿里云-智能语音交互-一句话识别 接口来进行语音识别，无时长限制。
- 识别过程中采用WebSocket直连阿里云，语音数据无需经过自己服务器。
- 自己服务器仅需提供一个Token生成接口即可（本库已实现一个本地测试NodeJs后端程序 [/assets/demo-asr](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](assets/demo-asr)，配置好代码里的阿里云账号后，在目录内直接命令行执行`node NodeJsServer_asr.aliyun.short.js`即可运行提供本地测试接口；
4. 前端调用ASR_Aliyun_Short，传入tokenApi，即可很简单的实现语音识别功能。

### ASR_Aliyun_Short文档
```
/* Detailed source-code truncated for AI context efficiency. */
```




[​](?)

[​](?)

## Sonic插件
[sonic.js](src/extensions/sonic.js)，38kb大小源码，音频变速变调转换，[参考此demo片段在线测试使用](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.sonic.transform)。此插件从[Sonic.java](https://github.com/waywardgeek/sonic/blob/71c51195de71627d7443d05378c680ba756545e8/Sonic.java)移植，并做了适当精简。

可到[assets/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](src/extensions/create-audio.nmn2pcm.js)，14kb大小源码，简单用 正弦波、方波、锯齿波、三角波 函数生成一段音乐简谱的pcm数据，主要用于测试时提供音频数据；[参考此demo片段在线测试使用](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=test.create-audio.nmn2pcm)。

### 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](src/extensions/dtmf.decode.js) + [lib.fft.js](src/extensions/lib.fft.js)、[dtmf.encode.js](src/extensions/dtmf.encode.js)，两个js一个解码、一个编码，体积小均不超过10kb，纯js实现易于移植。[参考此demo片段在线测试使用](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.dtmf.decode_and_encode)。

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转其他格式参考和测试](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.transform.wav2other)

生成的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基础教程](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=teach.pcm.basic)。

`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合并参考和测试+可移植源码](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.merge.wav_merge)


## mp3 (CBR) 格式
依赖文件：`mp3.js + mp3-engine.js`（或使用根目录的`recorder.mp3.min.js`一个文件即可），支持实时编码（边录边转码），采用的是[lamejs](https://github.com/zhuker/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转其他格式参考和测试](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.transform.mp32other)

**注意：** 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`（[参考](https://blog.csdn.net/u010650845/article/details/53520426)），数据帧之间可以直接拼接。因此将小的mp3片段文件的二进制数据全部合并到一起即可得到长的mp3文件；要求待合成的所有mp3片段的采样率和比特率需一致。[mp3合并参考和测试+可移植源码](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.merge.mp3_merge)

*注：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](https://github.com/higuma/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不支持（[真的假的](https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats#Browser_compatibility)）。

## amr (NB 窄带) 格式
依赖文件：`beta-amr.js + beta-amr-engine.js`，支持实时编码（边录边转码），采用的是[benz-amr-recorder](https://github.com/BenzLeung/benz-amr-recorder)(MIT License)优化后的[amr.js](https://github.com/jpemartins/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/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](./assets/node-localServer) 目录内提供了一个nodejs服务，实现了http上传接口、websocket接口。多个demo中有使用到，方便测试。

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

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



## WebRTC语音通话聊天demo
在[线测试Demo](https://xiangyuecn.github.io/Recorder/)中还包含了一个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 一下。

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

---

### Overview (`app-support-sample/README.md`)

**【[源GitHub仓库](https://github.com/xiangyuecn/Recorder/tree/master/app-support-sample)】 | 【[Gitee镜像库](https://gitee.com/xiangyuecn/Recorder/tree/master/app-support-sample)】本文档中默认使用[github.io部署的链接](https://xiangyuecn.github.io/Recorder/app-support-sample/)，如果无法访问，请直接下载仓库源码到本地然后双击打开html文件访问。**


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

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

使用场景|平台环境|参考Demo
:-:|-|-
**App+H5页面**|浏览器、WebView（App、软件内置浏览器）|[ [H5在线测试](https://xiangyuecn.github.io/Recorder/app-support-sample/) ] [ [QuickStart](https://xiangyuecn.github.io/Recorder/app-support-sample/QuickStart.html) ] [ [App vue](https://xiangyuecn.github.io/Recorder/assets/demo-vue/recordapp.html) ] <br> [ [Android App](demo_android) ] [ [iOS App](demo_ios) ]
**微信小程序**|微信小程序（支持可视化插件）|使用文档和Demo: [ [miniProgram-wx](./miniProgram-wx) ]
**uni-app**|可编译成：H5、Android iOS App、小程序|使用文档和Demo: [ [demo_UniApp](./demo_UniApp) ]
**NodeJs**|无录音功能，可对PCM数据实时处理、编码|测试代码: [ [node-codes](../assets/node-codes) ]
其他环境|可通过`RecordApp.RegisterPlatform`自行适配|自行对接




[​](?)

[​](?)

[​](?)

[​](?)


<p align="center"><a href="https://github.com/xiangyuecn/Recorder"><img width="100" src="https://gitee.com/xiangyuecn/Recorder/raw/master/assets/icon.png" alt="Recorder logo"></a></p>

<p align="center">
  <a title="Stars" href="https://github.com/xiangyuecn/Recorder"><img src="https://img.shields.io/github/stars/xiangyuecn/Recorder?color=0b1&logo=github" alt="Stars"></a>
  <a title="Forks" href="https://github.com/xiangyuecn/Recorder"><img src="https://img.shields.io/github/forks/xiangyuecn/Recorder?color=0b1&logo=github" alt="Forks"></a>
  <a title="npm Downloads" href="https://www.npmjs.com/package/recorder-core"><img src="https://img.shields.io/npm/dt/recorder-core?color=0b1&logo=npm" alt="npm Downloads"></a>
  <a title="npm Version" href="https://www.npmjs.com/package/recorder-core"><img src="https://img.shields.io/npm/v/recorder-core?color=0b1&logo=npm" alt="npm Version"></a>
  <a title="License" href="https://github.com/xiangyuecn/Recorder/blob/master/LICENSE"><img src="https://img.shields.io/github/license/xiangyuecn/Recorder?color=0b1&logo=github" alt="License"></a>
  <a title="JsDelivr CDN" href="https://www.jsdelivr.com/package/gh/xiangyuecn/Recorder"><img src="https://img.shields.io/badge/CDN-JsDelivr-0b1" alt="JsDelivr CDN"></a>
  <a title="unpkg CDN" href="https://unpkg.com/recorder-core/"><img src="https://img.shields.io/badge/CDN-unpkg-0b1" alt="unpkg CDN"></a>
  <a title="51LA" href="https://web.51.la/report/main?comId=20469973"><img src="https://img.shields.io/badge/51LA-available-0b1" alt="51LA"></a>
</p>


[​](?)

# :open_book:快速使用

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

[​](?Ref=ImportCode&Start)可以使用`import`、`require`、`html script`等你适合的方式来引入js文件，下面的以import为主要参考，其他引入方式根据文件路径自行调整一下就可以了。
```
/* Detailed source-code truncated for AI context efficiency. */
```
[​](?RefEnd)

### 在App WebView、H5页面中使用
可以通过阅读和运行本目录下的[QuickStart.html](https://xiangyuecn.github.io/Recorder/app-support-sample/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](./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_UniApp) demo项目，可编译成：H5、Android App、iOS App、微信小程序，**[AD]编译成App时仅供测试，正式发布需商用授权**。

在uni-app中使用，需要先导入Recorder-UniCore组件，在demo_UniApp项目中可以找到此组件，或者到[DCloud 插件市场下载此组件](https://ext.dcloud.net.cn/plugin?name=Recorder-UniCore)。
``` 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项目
```


[​](?)

[​](?)

## 二、调用录音
[​](?Ref=Codes&Start)RecordApp的基础调用方式在所有平台环境下是通用的；但不同环境下可能会提供更多的方法、或配置参数以供使用，多出来的请参考对应的平台环境支持说明。

```
/* Detailed source-code truncated for AI context efficiency. */
```
[​](?RefEnd)




[​](?)

[​](?)

## 【附】需要进行的权限处理
App的WebView中打开网页进行录音时，如果未使用原生录音，将默认使用Recorder H5进行录音（和普通网页录音没有任何区别），此时必须在`RecordApp.RequestPermission`前先获取到App的系统录音权限，如果App没有系统录音权限，将无法录音。

### Android App录音权限
[demo_android](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"/>
```

2. WebView的WebChromeClient中实现`onPermissionRequest`网页授权请求

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

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

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

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


### iOS App录音权限
[demo_ios](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接口](https://developer.apple.com/documentation/webkit/wkuidelegate)，可参考iOS Demo中的[MainView.swift](demo_ios/recorder/MainView.swift)中的代码；如果未实现，会导致H5录音每次打开页面后第一次录音时、或长时间无操作再打开录音时均会弹出录音权限对话框。

iOS 11.0-14.2：纯粹的H5录音在iOS WebView中是不支持的，需要有Native层的支持，具体参考[demo_ios](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`

<img src="https://gitee.com/xiangyuecn/Recorder/raw/master/assets/qq_group_781036591.png" width="220px">








[​](?)

[​](?)

[​](?)

[​](?)

# :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)`。

```
/* Detailed source-code truncated for AI context efficiency. */
```


## 【静态方法】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](../src/app-support/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](./native-config.js) 配置文件，还有参考 [demo_android](./demo_android) 和 [demo_ios](./demo_ios) 两个demo项目，copy源码改改就能用。

### 可使用的Recorder插件
浏览器环境中使用时，所有的Recorder插件均可以使用；非浏览器环境中部分依赖浏览器的插件将不可用（如：可视化、播放器）。

### 【静态属性】RecordApp.AlwaysAppUseH5
默认为false，设为true时：App里面总是使用H5网页版录音，忽略原生录音。



[​](?)

[​](?)

## 微信小程序：app-miniProgram-wx-support.js
[app-miniProgram-wx-support.js](../src/app-support/app-miniProgram-wx-support.js) 是微信小程序支持文件，支持在微信小程序环境中使用；使用请参考本目录内的 [miniProgram-wx](./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](./demo_UniApp)项目中可以找到Recorder-UniCore组件，或者到[DCloud 插件市场下载此组件](https://ext.dcloud.net.cn/plugin?name=Recorder-UniCore)，路径为 `@/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](./demo_UniApp)的文档。

### 可使用的Recorder插件
编译成H5时，所有插件均可使用。

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

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







[​](?)

[​](?)

[​](?)

[​](?)

# :open_book:国际化多语言支持（i18n）
RecordApp共用`Recorder.i18n`实现，因此只需配置Recorder的语言即可；支持的语言文件在 [/src/i18n](../src/i18n) 目录内，详细使用请参考Recorder文档。

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

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




[​](?)

[​](?)

[​](?)

[​](?)

# :star:捐赠
如果这个库有帮助到您，请 Star 一下。

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

---

### Overview (`app-support-sample/demo_UniApp/README.md`)

[Recorder](https://github.com/xiangyuecn/Recorder/) | [RecordApp](https://github.com/xiangyuecn/Recorder/tree/master/app-support-sample)

# :open_book:uni-app内使用RecordApp录音

本目录内包含uni-app的测试项目源码，主要文件请参考 [main_recTest.vue](pages/recTest/main_recTest.vue) ；本测试项目是使用[app-uni-support.js](uni_modules/Recorder-UniCore/app-uni-support.js)来给`RecordApp`提供uni-app支持的，此文件在 `uni_modules/Recorder-UniCore` 组件内，copy即可使用，或者到[DCloud 插件市场下载此组件](https://ext.dcloud.net.cn/plugin?name=Recorder-UniCore)。

- 支持vue2、vue3、nvue
- 支持编译成：H5、Android App、iOS App、鸿蒙App、微信小程序
- 支持已有的大部分录音格式：mp3、wav、pcm、amr、ogg、g711a、g711u等
- 支持实时处理，包括变速变调、实时上传、ASR语音转文字
- 支持可视化波形显示；可配置回声消除、降噪；**注意：不支持通话时录音**
- 支持PCM音频流式播放、完整播放，App端用原生插件边录音边播放更流畅
- 支持离线使用，本组件和配套原生插件均不依赖网络
- App端有配套的[原生录音插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin)可供搭配使用，兼容性和体验更好（暂无鸿蒙原生）

**[AD]app-uni-support.js文件在uni-app中编译到App平台时仅供测试用（App平台包括：Android App、iOS App），不可用于正式发布或商用，正式发布或商用需先到DCloud插件市场购买[此带授权的插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin-Android)（费用为¥199元，赠送Android版原生插件）获得授权许可**；编译到其他平台时无此授权限制，比如：H5、小程序，均为免费授权；详情参考本文档下面的授权许可章节。



## 测试方法
**本测试项目如果在HBuilder中编译失败，请删掉node_modules目录重新手动执行npm install（偶尔出现HBuilder自动创建项目依赖包不完整，导致无法编译）**

1. 在本测试项目根目录执行 `npm install --registry=https://registry.npmmirror.com/` ，完成`recorder-core`依赖的安装
2. 在HBuilder中打开本测试项目文件夹
3. 在HBuilder中运行到浏览器、手机、微信小程序，即可在不同环境下测试
4. 测试中提供了：基础录音、播放、上传、WebSocket实时语音通话对讲、ASR语音识别等功能


## 截图





[​](?)

[​](?)

[​](?)

[​](?)

# 集成到自己项目中

## 一、引入js文件
1. 在你的项目根目录安装`recorder-core`：`npm install recorder-core --registry=https://registry.npmmirror.com/`
2. 导入Recorder-UniCore组件：直接复制本目录下的`uni_modules/Recorder-UniCore`组件到你项目中，或者到[DCloud 插件市场下载此组件](https://ext.dcloud.net.cn/plugin?name=Recorder-UniCore)
3. 项目配置好录音权限，参考下面的录音权限配置章节，**特别注意App后台录音配置、小程序权限声明**
4. 在需要录音的vue文件script内编写以下代码，按需引入需要的js

```
/* Detailed source-code truncated for AI context efficiency. */
```

5. 编译成app时，默认需要额外提供一个renderjs模块，请照抄下面这段代码放到vue文件末尾

``` html

<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>

```


[​](?)

[​](?)

## 二、调用录音
```
/* Detailed source-code truncated for AI context efficiency. */
```






[​](?)

[​](?)

[​](?)

[​](?)

# 录音权限配置、需要注意的细节
## 编译成App时录音和权限
编译成App录音时，需要在uni-app项目的 `manifest.json` 中配置好Android、iOS、鸿蒙的录音权限声明：
```
//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中同样会存在；使用配套的[原生录音插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin)或uts插件时无以上问题和版本限制（uts插件开发中暂不可用），Android也无以上问题。

当App中音频编码是在renderjs中进行处理时，录音结束后会将整个录音文件传回逻辑层，由于uni-app的逻辑层和renderjs层大点的文件传输会比较慢，**建议Start时使用takeoffEncodeChunk实时获取音频文件数据可避免Stop时产生超大数据回传**；配置了`RecordApp.UniWithoutAppRenderjs=true`后，因为音频编码直接是在逻辑层中进行，将不存在传输性能损耗，但会影响逻辑层的性能（正常情况轻微不明显），需要配套使用原生录音插件才可以进行此项配置（暂无鸿蒙原生）。


[​](?)

## 编译成微信小程序时录音和权限
编译成微信小程序时，录音功能由小程序的`RecorderManager`提供，屏蔽了微信原有的底层细节（无录音时长限制）。

小程序录音需要用户授予录音权限，调用`RecordApp.RequestPermission`的时候会检查是否能正常录音，如果用户拒绝了录音权限，会进入错误回调，回调里面你应当编写代码检查`wx.getSetting`中的`scope.record`录音权限，然后引导用户进行授权（可调用`wx.openSetting`打开设置页面，方便用户给权限）。

**注意：上架小程序需要到小程序管理后台《[用户隐私保护指引](https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/miniprogram-intro.html)》中声明录音权限，否则正式版将无法调用录音功能（请求权限时会直接走错误回调）。**

更多细节请参考 [miniProgram-wx](../miniProgram-wx) 测试项目文档。


[​](?)

## 编译成H5时录音和权限
编译成H5时，录音功能由Recorder H5提供，无需额外处理录音权限。


[​](?)

## PCM音频流式播放、语音通话、回声消除、声音外放
在App、H5中，均可使用H5版的[BufferStreamPlayer](../../src/extensions/buffer_stream.player.js)来实时流式播放语音；其中App中需要在renderjs中加载BufferStreamPlayer，在逻辑层中调用`RecordApp.UniWebViewVueCall`等方法将逻辑层中接收到的实时语音数据发送到renderjs中播放；播放声音的同时进行录音，声音可能会被录进去产生回声，因此一般需要打开回声消除；调用代码参考demo中的[test_realtime_voice.vue](pages/recTest/test_realtime_voice.vue)。

App中如果搭配使用了配套的[原生录音插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin)，可以调用原生实现的PcmPlayer播放器实时流式播放PCM音频，边录音边播放更流畅；同时也支持完整播放，比如AI语音合成的播放；调用代码参考demo中的[test_player_nativePlugin_pcmPlayer.vue](pages/recTest/test_player_nativePlugin_pcmPlayer.vue)。

微信小程序请参考 [miniProgram-wx](../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不支持切换
```





[​](?)

[​](?)

[​](?)

# :open_book:国际化多语言支持（i18n）
RecordApp共用`Recorder.i18n`实现，因此只需配置Recorder的语言即可；支持的语言文件在 [/src/i18n](../../src/i18n) 目录内，详细使用请参考Recorder文档。

比如切换成英文：先`import "recorder-core/src/i18n/en-US.js"`，Recorder-UniCore组件内也有一个`i18n`目录 `import "@/uni_modules/Recorder-UniCore/i18n/en-US.js"`，然后配置`Recorder.i18n.lang="en-US"`即可。






[​](?)

[​](?)

[​](?)

[​](?)

# RecordApp增加的属性和方法文档
RecordApp基础的属性和方法请阅读[RecordApp文档](../)，比如`RequestPermission`、`Start`、`Stop`这些方法。下面的属性和方法是uni-app支持文件给RecordApp增加的，引入`app-uni-support.js`文件后才可以使用。

**下面方法中用到的componentThis参数**，一般直接使用this即可，如果你使用的vue3 setup组合式 API (Composition API) 编写时，请在import后面取到当前实例this（参考上面调用录音的代码中的vue3This变量定义），在需要componentThis的地方传vue3This变量即可。

**下面的方法中，如果写了`App 逻辑层中调用`，就只能在App环境下，并且只能在逻辑层中调用，在H5、renderjs等环境下调用将返回错误或显示错误日志，但不会抛异常；写了`renderjs层中调用`只能在App环境下的renderjs中进行调用；未写类似字样的，一般没有调用限制，在哪调用都行。**

## 【静态方法】RecordApp.UniIsApp()
判断当前环境是否是app，非app返回0，Android返回1，iOS返回2，鸿蒙返回3

## 【静态方法】RecordApp.UniPageOnShow(componentThis)
所有平台逻辑层中调用，当使用到录音的页面onShow时反复进行调用，传入当前页面或组件的this对象；如果是微信小程序环境，还会自动调用`RecordApp.MiniProgramWx_onShow`

## 【静态方法】RecordApp.UniWebViewActivate(componentThis)
App 逻辑层中调用，切换使用当前页面或组件的WebView，传入当前页面或组件的this对象，App环境中在调用本方法后才可以进行RecordApp.RequestPermission、RecordApp.Start操作

## 【静态方法】RecordApp.UniRenderjsRegister(moduleThis)
App renderjs中的mounted内调用，传入当前模块的this，一个renderjs模块只需调用一次即可

## 【静态方法】RecordApp.UniSaveLocalFile(fileName,buffer,True,False)
App 逻辑层、小程序环境下保存文件到本地，提供文件名和arrayBuffer，True(savePath)成功保存后回调完整保存路径，False(errMsg)；app保存的文件夹为`plus.io.PUBLIC_DOWNLOADS` 鸿蒙为`Context.filesDir/RecUniSaveFiles`，小程序为 `wx.env.USER_DATA_PATH` 路径

## 【静态方法】RecordApp.UniFindCanvas(componentThis,selectorList,renderjsFunc,mainFunc)
所有平台逻辑层中均可调用，查找用于可视化绘制的canvas实例

> App、H5会在原位置添加新的html canvas节点，并且隐藏老的canvas，原因是uni创建的canvas不可以使用：如果是隐藏的uni-canvas没法初始化，到显示的时候会篡改canvas已设置的宽高，App中uni-canvas显示有问题 似乎设置了缩放 导致显示不全，重新建一个省心还高效
> 小细节：如果你的canvas所在的view存在v-if之类的会重新创建了对应的view，必须将可视化波形重新进行初始化才能使用；如果使用的是同一个view，重新初始化后如果上次的动画没有完成时，小程序中开头部分新的波形会和老的波形相互干扰，老动画完成后恢复正常，App、H5无此问题（因为会创建新的canvas节点）

``` javascript
// componentThis:this 当前页面或当前组件this
// selectorList:[".class"] 要查找的canvas列表，提供css选择器，查找得到canvas1 canvas2...
// renderjsFunc:"eval js" App环境下在renderjs中进行绘制时执行的js代码，可以使用结果变量canvas1 canvas2...，代码中的this为renderjs模块的this
// mainFunc:fn(canvas1,canvas2...) 逻辑层中进行绘制时调用函数（H5、小程序等），参数为查找到的canvas

/**调用示例代码，App、H5、小程序通用
假设有以下2个canvas的view，可以一次性查找到这两个canvas，canvas需要指定宽高（下面style里指定了300*100）
<view>
    <canvas type="2d" class="recwave-SurferView" style="width:300px;height:100px"></canvas>
    <canvas type="2d" class="recwave-SurferView-2x" style="width:600px;height:100px;display:none"></canvas>
</view>**/

//App环境下是在renderjs中绘制，H5、小程序等是在逻辑层中绘制，因此需要提供两段相同的代码（宽高值需要和canvas style的宽高一致）
RecordApp.UniFindCanvas(this,[".recwave-SurferView",".recwave-SurferView-2x"],`
    this.surferView=Recorder.WaveSurferView({compatibleCanvas:canvas1,compatibleCanvas_2x:canvas2, width:300, height:100});
`,(canvas1,canvas2)=>{
    this.surferView=Recorder.WaveSurferView({compatibleCanvas:canvas1,compatibleCanvas_2x:canvas2, width:300, height:100});
});
```

## 【静态属性】RecordApp.UniJsSource.IsSource
当前`app-uni-support.js`文件是否是源码版，true为源码版，false为压缩版

## 【静态属性】RecordApp.UniWithoutAppRenderjs
App 逻辑层中可设置，在不要使用或没有renderjs时，应当设为true，此时App中RecordApp完全运行在逻辑层，比如nvue页面，此时音频编码之类的操作全部在逻辑层，但会影响逻辑层的性能（正常情况轻微不明显），需要提供UniNativeUtsPlugin配置由原生插件进行录音，可视化绘制依旧可以在renderjs中进行。默认为false，RecordApp将在renderjs中进行实际的工作（音频编码会利用WebWorker加速），然后将处理好的数据传回逻辑层，数据比较大时传输会比较慢（可通过Start时使用takeoffEncodeChunk实时获取音频文件数据可避免Stop时产生超大数据回传）。

搭配了原生插件，且需要在后台录音时，建议设为true，可避免受renderjs的WebView在后台运行受限的影响，录音会更稳定。（暂无鸿蒙原生插件）

## 【静态属性】RecordApp.UniAppUseLicense
App 逻辑层中可设置，`app-uni-support.js`文件在App中使用的授权许可，默认为空字符串，获得授权后请赋值为"我已获得UniAppID=***的商用授权"（星号为你项目的uni-app应用标识），设置了UniNativeUtsPlugin时默认为已授权；如果未授权，将会在App打开后第一次调用`RecordApp.RequestPermission`请求录音权限时，弹出“未获得商用授权时，App上仅供测试”提示框。


[​](?)

## 二进制和Base64相关
更多转换请参考：[【Demo库】js二进制转换-Base64/Hex/Int16Array/ArrayBuffer/Blob](https://xiangyuecn.github.io/Recorder/assets/工具-代码运行和静态分发Runtime.html?jsname=lib.js-binary-bytes)

### 【静态方法】RecordApp.UniBtoa(arrayBuffer)
base64编码，将arrayBuffer二进制数据转为base64字符串

### 【静态方法】RecordApp.UniAtob(b64)
base64解码，将base64字符串转为arrayBuffer二进制数据

### 【静态方法】RecordApp.UniB64Enc(str)
base64编码，将字符串转为base64字符串

### 【静态方法】RecordApp.UniB64Dec(b64)
base64解码，将base64字符串转为字符串

### 【静态方法】RecordApp.UniStr2Buf(str)
将文本转成arrayBuffer

### 【静态方法】RecordApp.UniBuf2Str(arrayBuffer)
将arrayBuffer转成文本


[​](?)

## App逻辑层与renderjs层交互

### 【静态方法】RecordApp.UniWebViewCallAsync(componentThis,set,jsCode,bigBytes)
简化 App 逻辑层中对此页面或组件的WebView renderjs层的调用，并异步返回调用结果，出错会抛异常（componentThis为null时使用UniWebViewActivate切换的页面或组件）；方法内部对 UniWebViewEval、UniWebViewVueCall、UniMainCallBack、UniWebViewSendToMain 等方法的调用进行了封装，使用更简单

``` javascript
//调用示例代码（在async函数内调用）
var dataMB=new Uint8Array(1*1024*1024); //可选的，假设同时需要传递1MB的数据到renderjs中
var set={ //可选配置
    tag:"格式转码" //可选调用标记，抛异常时会添加到错误消息开头，默认为空字符串
    ,timeout:5000 //可选调用超时，默认5秒超时，-1不超时
    ,useEval:false //可选是否要通过UniWebViewEval来执行jsCode，默认false使用UniWebViewVueCall来执行jsCode（此时jsCode中的this为renderjs模块的this）
};
try{
    var result=await RecordApp.UniWebViewCallAsync(this,set,`
        var buffer=BigBytes; //接收到了逻辑层的二进制数据
        console.log("renderjs已执行 "+buffer.byteLength+" "+typeof(this.$ownerInstance));
        //调用逻辑层vue的方法，如果配置了useEval=true将没有this
        //this.$ownerInstance.callMethod("test",{data:{}});
        
        //处理完后，需调用CallSuccess(value,bigBytes)、CallFail(errMsg)返回结果给逻辑层，不然会抛处理超时异常
        //CallFail("转码错误xxxx"); //处理失败就调用CallFail，逻辑层会抛出此错误异常

        //CallSuccess(any); //处理成功回调，可传入任意值，逻辑层将接收到这个值 result=any
        CallSuccess({abc:123}, new Uint8Array(9).buffer); //可以额外返回一个ArrayBuffer，逻辑层将接收到一个对象 result={ value:{abc:123}, bigBytes:ArrayBuffer }
    `, dataMB.buffer);
}catch(e){
    result={timeout:!!e.isTimeout, errMsg:e.message};
}
uni.showModal({title:"renderjs调用结果", content:JSON.stringify(result)});
```

### 【静态方法】RecordApp.UniWebViewEval(componentThis,jsCode,bigBytes)
App 逻辑层中直接调用此页面或组件的WebView renderjs中的eval（componentThis为null时使用UniWebViewActivate切换的页面或组件）；要调用renderjs模块vue组件内的方法请用UniWebViewVueCall；如果需要传递大的数据请用bigBytes参数传入一个ArrayBuffer，jsCode中使用BigBytes变量得到这个数据

``` javascript
//调用示例代码，非频繁调用时建议使用UniWebViewCallAsync简化这些调用
var cb=RecordApp.UniMainCallBack((data)=>{ //可选的，renderjs执行完成后回调
    uni.showModal({title:"收到了renderjs回调", content:JSON.stringify(data)});
});
var dataMB=new Uint8Array(1*1024*1024); //可选的，假设同时需要传递1MB的数据到renderjs中

RecordApp.UniWebViewEval(this,`
    var buffer=BigBytes; //接收到了逻辑层的二进制数据
    console.log("renderjs已执行 "+buffer.byteLength); //这里是WebView浏览器环境，随便调用
    
    //处理完后，可以回调结果给逻辑层
    RecordApp.UniWebViewSendToMain({action:"${cb}", abc:buffer.byteLength});
`, dataMB.buffer);
```

### 【静态方法】RecordApp.UniWebViewVueCall(componentThis,jsCode,bigBytes)
App 逻辑层中直接调用此页面或组件的renderjs模块vue组件内的方法（componentThis为null时使用UniWebViewActivate切换的页面或组件），jsCode中的this为renderjs模块的this（也可以用This变量）（如需renderjs中调用逻辑层vue实例方法，请直接用$ownerInstance.callMethod即可）；如果需要传递大的数据请用bigBytes参数传入一个ArrayBuffer，jsCode中使用BigBytes变量得到这个数据

``` javascript
//调用示例代码，非频繁调用时建议使用UniWebViewCallAsync简化这些调用
//jsCode可以传个对象{preCode:"",jsCode:""}，preCode相当于UniWebViewEval调用，会在获取到vue上下文前执行，获取失败时jsCode无法执行，preCode中可重新赋值CallErr函数来绑定失败回调
var cb=RecordApp.UniMainCallBack((data)=>{ //可选的，renderjs执行完成后回调
    uni.showModal({title:"收到了renderjs回调", content:JSON.stringify(data)});
});
var dataMB=new Uint8Array(1*1024*1024); //可选的，假设同时需要传递1MB的数据到renderjs中

RecordApp.UniWebViewVueCall(this,`
    var buffer=BigBytes; //接收到了逻辑层的二进制数据
    console.log("renderjs已执行 "+buffer.byteLength+" "+typeof(this.$ownerInstance));
    //调用逻辑层vue的方法
    //this.$ownerInstance.callMethod("test",{data:{}});
    
    //处理完后，可以回调结果给逻辑层
    RecordApp.UniWebViewSendToMain({action:"${cb}", abc:typeof(this.$ownerInstance)});
`, dataMB.buffer);
```

### 【静态方法】RecordApp.UniMainCallBack(callback)
App 逻辑层中生成一个一次性的回调，renderjs层通过这个回调返回数据给逻辑层，函数参数为renderjs层返回的数据，数据应当是个对象，错误消息统一用errMsg属性，主要和UniWebViewSendToMain一块搭配使用，请参考上面的UniWebViewEval；由于这个回调只能回调一次，如果需要多次回调请用UniMainCallBack_Register

### 【静态方法】RecordApp.UniMainCallBack_Register(key,callback)
App 逻辑层注册或取消注册key对应的一个回调，此回调可以反复被调用，使用上和UniMainCallBack相同；`key="abc"`提供一个唯一的字符串，同一个key后注册的会覆盖以前的，callback为null时会清除此key对应的回调

### 【静态方法】RecordApp.UniWebViewSendToMain(data)
renderjs层中调用本方法，将数据传回给逻辑层回调，`data={action:"UniMainCallBack等",...需要返回的数据, errMsg:"错误消息"}`，使用请参考上面的UniWebViewEval

### 【静态方法】RecordApp.UniWebViewSendBigBytesToMain(arrayBuffer,True,False)
renderjs层调用本方法，将超过512KB的二进制数据传回逻辑层，一次性发送1MB以上的数据UniApp太卡，传入arrayBuffer，True(dataID), False(errMsg)，成功后可在逻辑层中通过UniMainTakeBigBytes(dataID)来取到数据

### 【静态方法】RecordApp.UniMainTakeBigBytes(dataID)
App 逻辑层取走接收到的二进制数据，返回arrayBuffer，不可重复提取否则返回null



[​](?)

## 原生录音插件配置
### 【静态属性】RecordApp.UniNativeUtsPlugin
App 逻辑层中可设置，App中启用原生录音插件或uts插件，由App提供原生录音，将原生插件或uts插件赋值给这个变量即可开启支持；使用原生录音插件只需赋值为`{nativePlugin:true}`即可（提供`nativePluginName`可指定插件名字，默认为`Recorder-NativePlugin`），使用uts插件只需import插件后赋值即可（uts插件还未开发，目前不可集成，暂无鸿蒙原生插件）；如果未提供任何插件，App中将使用H5录音（在renderjs中提供H5录音）。

**在App中引入原生录音插件来进行录音，兼容性和体验更好，在iOS上的体现更为明显（请参考上面的录音权限描述）。**

### 集成原生录音插件
原生录音插件已上架DCloud插件市场，请到市场购买或试用；测试时无需购买插件，试用是免费的，并且原生插件试用是无限制的。（暂无鸿蒙原生插件）

1. 到DCloud插件市场试用或购买[这个原生录音插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin)
2. 在项目manifest.json的App原生插件配置中勾选上这个原生录音插件
3. 调试需要先打一个自定义基座，然后使用自定义基座进行调试；正式打包需要使用云打包


### 使用原生插件
``` javascript
//在调用RecordApp.RequestPermission之前进行配置，建议放到import后面直接配置（全局生效）
//也可以判断一下只在iOS上或Android上启用，不判断就都启用，比如判断iOS：RecordApp.UniIsApp()==2
RecordApp.UniNativeUtsPlugin={ //目前仅支持原生插件，uts插件不可用
    nativePlugin:true
    //,nativePluginName:"xxx" //可指定插件名字，默认为Recorder-NativePlugin
};

//配置好后可调用RecordApp.UniCheckNativeUtsPluginConfig()来检查配置是否能生效，返回空字符串代表正常，如果无法加载原生录音插件会返回错误消息
//配置好后在调用录音功能时，会使用原生插件来录音
```

### 【静态方法】RecordApp.UniNativeUtsPluginCallAsync(action,args)
App 逻辑层中可调用，在集成了原生插件时，在App中可以通过本方法调用原生插件的部分扩展能力，主要为文件流读写。本方法异步返回调用结果，出错会抛异常。

### 【静态属性】RecordApp.UniNativeUtsPlugin_JsCall=(data)=>{ }
App逻辑层中可绑定一个全局的函数回调，来接收原生插件的`jsCall`事件回调，通过`data.action`来区分不同事件；可以从这个函数里面自己写代码分发事件，比如用uni.$emit触发由uni.$on绑定的回调；不推荐使用，请直接用下面这个OnJsCall方法绑定事件。

### 【静态方法】RecordApp.UniNativeUtsPlugin_OnJsCall(key,action,callback)
App逻辑层中绑定原生插件某个事件的回调，相同的页面和处理功能请使用相同的key，相同key+action只会绑定最后的一次（建议页面创建时用一个随机字符串做key并绑定、销毁时解除），call为空时解除绑定，当绑定的原生层jsCall发来数据时，action相同就会触发回调；事件请参考下面的【事件回调】，调用代码参考demo中的[test_player_nativePlugin_pcmPlayer.vue](pages/recTest/test_player_nativePlugin_pcmPlayer.vue)。

### 可用的接口
```
/* Detailed source-code truncated for AI context efficiency. */
```


### Android后台录音保活
自`Android 9`开始，为了保护用户隐私，锁屏或进入后台一段时间后App可能会被**禁止访问麦克风**、摄像头等功能，导致无法录音、或录音数据全部是静音，因此需要使用保活机制才能在后台录音；**H5录音、原生接口录音均受影响**。iOS不存在此类问题，iOS只要在manifest.json的`后台运行能力`中提供`audio`配置，即可在后台录音。

本插件已提供androidNotifyService接口，来控制搭配常驻通知的Android后台录音保活服务，打开服务后可提升App在后台或锁屏后录音的稳定性，注意：不同机型的录音**保活效果不一定100%有效**；此服务为前台服务（Foreground services），在启动的时候会在状态栏显示一个常驻的通知（通知可能不显示或会延迟显示，并不影响服务运行）。

【注意】targetSdkVersion>=31(`Android 12+`)时，App必须在前台才能调用此服务接口，在后台时不允许调用；targetSdkVersion>=34(`Android 14+`)时，`microphone`类型的服务依赖录音权限，因此建议先请求录音权限（调用RecordApp.RequestPermission、或recordPermission接口），成功后再来调用androidNotifyService接口。

【注意】使用前台服务后，在上架时可能需要声明需要使用的任何前台服务类型、提供使用说明和具体用例，否则可能导致部分应用市场**无法上架**；为了不影响不使用此服务的App，要使用本服务前要在AndroidManifest.xml中进行**配置后才能使用**。

``` xml
//请在项目根目录新建AndroidManifest.xml文件，填入以下内容（修改后需要重新打包才会生效）
//文档篇幅有限，下面内容的原文在demo项目的同名文件中，可在demo中找到此文件，或到github查看：https://github.com/xiangyuecn/Recorder/blob/master/app-support-sample/demo_UniApp/AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" 
    package="io.dcloud.nativeresouce">
    
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"/>

    <application>
        <service
            android:name="uni.plugin.recorder.RecorderNativePlugin$RecNotifyService"
            android:foregroundServiceType="microphone"
            android:exported="false">
        </service>
    </application>
</manifest>

//上面这段代码仅用于Android后台录音保活服务，如果项目不需要后台录音保活，可以不复制
//iOS只要在manifest.json的`后台运行能力`中提供`audio`配置，即可在后台录音
```







[​](?)

[​](?)

[​](?)

[​](?)

# 使用Recorder-UniCore组件的授权许可
**app-uni-support.js文件在uni-app中编译到App平台时仅供测试用（App平台包括：Android App、iOS App、鸿蒙App），不可用于正式发布或商用，正式发布或商用需先到DCloud插件市场购买[此带授权的插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin-Android)（费用为¥199元，赠送Android版原生插件）获得授权许可**；编译到其他平台时无此授权限制，比如：H5、小程序，均为免费授权。

购买后可联系客服，同时提供订单信息，客服拉你进入VIP支持QQ群，在群文件中可下载Recorder-UniCore前端组件的`app-uni-support.js`文件最新源码；客服联系方式：QQ 1251654593 ，或者直接联系作者QQ 753610399 （回复可能没有客服及时）。

> 注：VIP支持群的主要作用是代表你已获得授权许可，可以随时获得`app-uni-support.js`文件最新版源码；不作为问答或售后群使用，当然如果你有问题也可以直接群里问，花费时间不多的，作者免费顺带就解答了，如果复杂花费比较久时间的，可能要适当收点人工费用，或者选择进行付费指导。
> 
> `Recorder-UniCore`组件中自带的`app-uni-support.js`文件是压缩版，功能和源码版一致，在VIP支持群中下载得到此文件源码后，可以直接替换组件中的这个文件，也可以不替换。


[​](?)

## 解除组件限制
如果未获得授权许可，将会在App打开后第一次调用`RecordApp.RequestPermission`请求录音权限时，弹出“未获得商用授权时，App上仅供测试”提示框。

获得作者的授权许可后，请在调用`RecordApp.RequestPermission`请求录音权限前，赋值`RecordApp.UniAppUseLicense="我已获得UniAppID=***的商用授权"`（星号为你项目的uni-app应用标识），就不会弹提示框了；或者购买了配套的[原生录音插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin)或uts插件，直接设置`RecordApp.UniNativeUtsPlugin`参数，也不会弹提示框。

插件开发维护不易，感谢支持~


[​](?)

## 许可及服务协议

**您（以下称“用户”）下载、使用我（以下称“作者”）提供的Recorder-UniCore组件（含原生录音插件、uts插件，以下统称“本组件”），应当阅读并遵守本许可协议。请用户务必审慎阅读、充分理解各条款内容，特别是免除或者限制责任的条款，并选择接受或不接受。除非用户已阅读并接受本协议所有条款，否则用户无权下载、使用本组件及相关服务，用户的下载、使用等行为即视为用户已阅读并同意本许可协议的约束。**

1. 用户应当直接从作者许可的途径，如作者的GitHub、Gitee仓库、已上架的DCloud插件市场、QQ群等途径中获取本组件；其他途径获取到的组件代码是未经过作者授权的，存在安全隐患，可能会导致你的程序、资产受到侵害，作者对因此给用户造成的损失不予负责。

2. 作者将积极并采取措施保护用户的信息和隐私；组件本身不会搜集存储任何用户信息。

3. 除法律法规有明确规定外，作者将尽最大努力确保本组件及其所涉及的技术及信息安全、有效、准确、可靠，但受限于现有技术，用户理解作者不能对此进行担保。

4. 用户理解，对于不可抗力及第三方原因导致的您的直接或间接损失，作者无法承担责任。

5. 用户因使用本组件进行生成、处理数据，由此引起或与有关的包括但不限于利润损失、资料损失、业务中断的损害赔偿或其它商业损害赔偿或损失，需由用户自行承担。

6. 如若发生赔偿、退款等行为，赔偿、退款等累计金额不得超过用户实际支付给作者的总金额。

7. 已授予的授权许可，包括免费授权，和已购买的原生录音插件、uts插件，均仅限在授权指定的uni-app的应用标识（AppID）对应的项目上使用，不可在其他项目上使用；用户不得对本组件及其中的相关信息擅自出租、出借、销售、逆向工程、破解，不得在未取得作者授权的情况下借助本组件发展与本组件有关联的衍生软件产品、服务、插件、外挂等。

8. 用户不得使用本组件从事违反法律法规政策、破坏公序良俗、损害公共利益的行为。

---

### Overview (`app-support-sample/demo_android/README.md`)

[Recorder](https://github.com/xiangyuecn/Recorder/) | [RecordApp](https://github.com/xiangyuecn/Recorder/tree/master/app-support-sample)

# :open_book:Android Hybrid App

目录内包含Android App测试源码，和核心文件 [RecordAppJsBridge.java](app/src/main/java/com/github/xianyuecn/recorder/RecordAppJsBridge.java) ；目录内 [app-debug.apk.zip](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](../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](app/src/main/java/com/github/xianyuecn/recorder/MainActivity.java)中的实现代码。

> 注：如果使用的`腾讯X5内核`，可能还须提供`android.permission.CAMERA`权限，和调用`webView.setWebChromeClientExtension`来重写X5的`IX5WebChromeClientExtension.onPermissionRequest`方法来进行权限处理


## Android后台录音保活
自`Android 9`开始，为了保护用户隐私，锁屏或进入后台一段时间后App可能会被禁止访问麦克风、摄像头等功能，导致无法录音、或录音数据全部是静音，因此需要使用保活机制才能在后台录音；H5录音、原生接口录音均受影响。iOS不存在此类问题，iOS只要在manifest.json的`后台运行能力`中提供`audio`配置，即可在后台录音。

如需后台录音就需要使用一个前台服务（[Foreground services](https://developer.android.google.cn/develop/background-work/services/fg-service-types?hl=zh-cn)）来作为搭配常驻通知的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端即可完成完整的录音功能。



## 【截图】

---

### Overview (`app-support-sample/demo_ios/README.md`)

[Recorder](https://github.com/xiangyuecn/Recorder/) | [RecordApp](https://github.com/xiangyuecn/Recorder/tree/master/app-support-sample)

# :open_book:iOS Hybrid App

本目录内包含iOS App测试源码，和核心文件 [RecordAppJsBridge.swift](recorder/RecordAppJsBridge.swift) ；clone后用`xcode`打开后编译运行。本demo为swift代码，兼容iOS 9.0+，已测试iOS 17.1。

本Demo是对[/app-support-sample/native-config.js](../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接口](https://developer.apple.com/documentation/webkit/wkuidelegate)（iOS 15+支持）才能做到静默授权支持，可参考[MainView.swift](recorder/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的好使。



## 运行截图

---

### Overview (`app-support-sample/miniProgram-wx/README.md`)

[Recorder](https://github.com/xiangyuecn/Recorder/) | [RecordApp](https://github.com/xiangyuecn/Recorder/tree/master/app-support-sample)

# :open_book:微信小程序内使用RecordApp录音

本目录内包含微信小程序的测试源码，主要文件**请直接参考 [recTest.js](pages/recTest/recTest.js) 代码**；使用[/src/app-support/app-miniProgram-wx-support.js](../../src/app-support/app-miniProgram-wx-support.js)来给`RecordApp`提供微信小程序支持，底层适配使用的微信的 `wx.getRecorderManager + PCM` ，屏蔽了微信原有的底层细节（无录音时长限制），统一使用`RecordApp`提供的方法进行录音，详细使用文档请直接参考[RecordApp文档](../)即可。

- 支持Android、iOS真机微信内测试，PC端微信支持情况未知（[见社区内2023-03-10回复说在修bug，后面又直接干脆说不支持](https://developers.weixin.qq.com/community/develop/doc/000ec4fb6a826883726fdf8605b000)）
- 支持微信开发者工具模拟器内测试（内置WebM解码，和真机无差异）
- 支持已有的大部分录音格式：mp3、wav、pcm、amr、ogg、g711a、g711u等（注意其中mp3、amr、ogg编码器会显著增加小程序包体积）
- 支持实时处理，包括变速变调、实时上传、ASR语音转文字
- 支持可视化波形显示

> 如果是在小程序`web-view`中录音请直接使用`Recorder H5`就行，和普通浏览器没有区别，不需要使用RecordApp进行额外的支持。



[​](?)

[​](?)

## 测试方法

1. 将Recorder根目录内的`src`文件夹复制到`copy-rec-src`文件夹内（比如可用路径：`/copy-rec-src/src/recorder-core.js`，自己实际项目按需copy仅你require引用到了的文件就行）
2. 修改`project.config.json`内的`appid`成你自己的测试号
3. 微信开发者工具中导入本项目，就能编译预览了



## 截图





[​](?)

[​](?)

# RecordApp增加的属性和方法文档
RecordApp基础的属性和方法请阅读[RecordApp文档](../)，比如`RequestPermission`、`Start`、`Stop`这些方法。下面的属性和方法是微信支持文件给RecordApp增加的，引入`app-miniProgram-wx-support.js`文件后才可以使用。

## 【静态方法】RecordApp.MiniProgramWx_onShow()
当使用到录音的页面onShow时进行一次调用，用于恢复被暂停的录音（比如按了home键会暂停录音）

## 【静态方法】RecordApp.MiniProgramWx_WriteLocalFile(fileName,buffer,True,False)
保存文件到小程序本地，提供文件名和arrayBuffer，True(savePath)成功保存后回调完整保存路径，False(errMsg)；保存后得到的savePath为 `wx.env.USER_DATA_PATH` 路径，可以用来播放、上传。

``` javascript
fileName可以是："xxx.mp3" "文件夹/xxx.mp3" 或 传一个配置对象{}
配置对象：{
    fileName:"" //必填，fileName字符串
    ,append:false //可选，是否追加写入到文件结尾，默认false会新建文件并写入数据
    ,seekOffset:-1 //可选，从现有文件指定位置写入并覆盖对应内容，取值：-1不指定（默认），0 到 文件长度（0为文件开头位置，append配置无效）
}

参考 pages/test_other__/test_other__.js 中的 realtimeWritePcm2Wav 方法
，有用到实时写入pcm数据，并在结束时在文件开头写入wav头，即可生成wav文件
```

## 【静态方法】RecordApp.MiniProgramWx_DeleteLocalFile(savePath,True,False)
删除已保存到本地的文件，savePath必须是WriteLocalFile得到的路径，True()删除成功回调，False(errMsg)删除失败回调




[​](?)

[​](?)

# 录音权限配置、需要注意的细节

**上架小程序需要到小程序管理后台《[用户隐私保护指引](https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/miniprogram-intro.html)》中声明录音权限，否则正式版将无法调用录音功能（请求权限时会直接走错误回调）。**

小程序自带的`RecorderManager`录音的时候，如果小程序退到了后台（或触发了`onInterruptionBegin`），录音将会被暂停，小程序显示的时候才允许继续录音，但`RecorderManager`无法感知这些事件或者存在bug，**因此你需要在要录音页面的`onShow`函数内加上`RecordApp.MiniProgramWx_onShow()`这行代码**，如果没有加这行代码，被暂停的录音可能会无法自动恢复。

小程序录音需要用户授予录音权限，调用`RecordApp.RequestPermission`的时候会检查是否能正常录音，如果用户拒绝了录音权限，会进入错误回调，回调里面你应当编写代码检查`wx.getSetting`中的`scope.record`录音权限，然后引导用户进行授权（可调用`wx.openSetting`打开设置页面，方便用户给权限）。

微信开发者工具对npm支持太差，“构建 npm”功能没什么卵用，npm包内没被main文件引用的js会被全部丢弃，导致文件缺失；似乎也不允许手动`require`含`node_modules`的路径；因此请直接copy根目录内的src文件夹到本小程序源码的`copy-rec-src`文件夹内，直接当做小程序源码调用，好使；或者按需copy仅你require引用到了的js文件到小程序项目中，免得小程序源码过大（一般编译时会忽略掉未引用到的js文件）。


[​](?)

## 语音通话、回声消除、声音外放

由于BufferStreamPlayer不支持在小程序中使用，如需实时播放语音，可参考[test_realtime_voice__.js](pages/test_realtime_voice__/test_realtime_voice__.js)中的`initStreamPlay`播放代码，底层使用`wx.createWebAudioContext().createBufferSource()`来进进行播放；播放声音的同时进行录音，声音可能会被录进去产生回声，因此一般需要打开回声消除。

配置audioTrackSet可尝试打开回声消除，或者切换成听筒播放，降低回声。
``` js
//打开回声消除，仅Android上会生效，实际为RecorderManager.start的audioSource参数
RecordApp.Start({
    ... 更多配置参数请参考RecordApp文档
    //配置echoCancellation后，相当于给android_audioSource配置为7
    ,audioTrackSet:{echoCancellation:true,noiseSuppression:true,autoGainControl:true}
    
    //Android指定麦克风源，0 DEFAULT 默认音频源，1 MIC 主麦克风，5 CAMCORDER 相机方向的麦，6 VOICE_RECOGNITION 语音识别，7 VOICE_COMMUNICATION 语音通信(带回声消除)
    ,android_audioSource:7 //提供此配置时优先级比audioTrackSet更高
});

//尝试切换成听筒播放，iOS不支持配置回声消除，可使用听筒播放，大幅减弱回声
wx.setInnerAudioOption({ speakerOn:false })
//尝试切换成扬声器外放
wx.setInnerAudioOption({ speakerOn:true })
```


[​](?)

## 已知问题

iOS上微信小程序基础库存在bug，canvas.drawImage(canvas)可能无法绘制，可能会导致可视化插件WaveSurferView在iOS小程序上不能正确显示，其他可视化插件、Android、开发工具均无此兼容性问题；相关链接：[issues#202](https://github.com/xiangyuecn/Recorder/issues/202)，[社区内2023-11-16说在修](https://developers.weixin.qq.com/community/develop/doc/000aaca2148dc8a235a0fb8c66b000)。


[​](?)

## 小程序包体积

必须引入的 Recorder + RecordApp (`src/recorder-core.js`、 `src/app-support/app.js + app-miniProgram-wx-support.js`)  本身并不大，编译出来的小程序包增量仅有35kb左右，可以放心引入。

其他文件全部是可选的，按需手动require引入：不同录音格式类型编码器、可视化插件等。

使用wav、pcm、g711录音格式编码器，这3个编码器加起来也增量仅有6kb左右，可以放心引入。

使用mp3录音格式编码器，编译出来的小程序包增量会有120kb左右，如果没有用到mp3格式，就不要require引入。

使用amr录音格式编码器，编译出来的小程序包增量会有250kb左右，比较大；使用ogg录音格式编码器，编译出来的小程序包增量会有700kb左右，太大了；不建议在小程序中使用这两种格式。

---

### Overview (`assets/demo-asr/README.md`)

# 测试用的本地ASR相关接口服务器
目前已提供：
- 阿里云-智能语音交互-一句话识别 生成token的api接口，本地测试http后端服务



## 阿里云一句话识别TokenApi
### 如何运行
双击`运行服务.cmd`选择对应菜单运行即可，或直接命令行执行 `node NodeJsServer_asr.aliyun.short.js` 运行服务，**注意：需要先在js文件内配置密钥**。

### 如何配置
用编辑器打开 `NodeJsServer_asr.aliyun.short.js` 文件，文件开头有 `【必填】` 项，根据注释完成配置：`AccessKey`、`Secret`、`Appkey`，可提供多个Appkey对应不同的语言模型。

http访问端口默认`9527`，如果配置了ssl证书可以使用https访问。



## 跨域问题、http无法访问？
非本机浏览器中测试H5页面时，比如手机，由于必须https访问（本机可用127.0.0.1、localhost地址无此问题，其他地址必须https才能录音），可能会存在跨域或https无法访问http的问题。因此建议均适用https方便测试；或者直接本机上打开http的接口地址，获得对应token然后手动复制json到页面中使用也是可以的。

本地测试时建议使用一个域名解析到本地服务器局域网ip，比如：192-168-1-123.xxx.com 解析到 192.168.1.123，用这个域名申请免费的https通配符证书，[在线免费申请（ZeroSSL、Let’s Encrypt）](https://xiangyuecn.github.io/ACME-HTML-Web-Browser-Client/ACME-HTML-Web-Browser-Client.html)

---

### Overview (`assets/demo-vue/README.md`)

# vue+webpack测试

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

- [Recorder H5在线测试](https://xiangyuecn.github.io/Recorder/assets/demo-vue)，主要文件为 [component/recorder.vue](https://github.com/xiangyuecn/Recorder/blob/master/assets/demo-vue/component/recorder.vue)，支持PC、Android、IOS 14.3+。
- [RecordApp 在线测试](https://xiangyuecn.github.io/Recorder/assets/demo-vue/recordapp.html)，主要文件为 [component/recordapp.vue](https://github.com/xiangyuecn/Recorder/blob/master/assets/demo-vue/component/recordapp.vue) [即将废弃] ，RecordApp除了Recorder支持的外，支持Hybrid App，低版本IOS上支持微信网页和小程序web-view。

# 运行方法
## 【1】编译vue源码
```
npm install
npm run build-dev
```
## 【2】浏览器访问
然后就可以打开`index.html`查看集成`Recorder`的效果，打开`recordapp.html`查看集成`RecordApp`的效果。

---

### Overview (`assets/node-localServer/README.md`)

# 测试用的本地服务器
提供http上传接口、websocket接口

## 运行
1. 先执行 `npm install --registry=https://registry.npmmirror.com/` 安装依赖
2. 执行 `npm run start` 运行服务

## 配置
- http端口默认 `9528`
- https端口默认 `9538`
- ws端口默认 `9529`
- wss端口默认 `9539`

在`main.js`中可以修改端口，如果要开启https、wss，需先在`main.js`中配置ssl证书文件路径；本地测试时建议使用一个域名解析到本地服务器局域网ip，比如：192-168-1-123.xxx.com 解析到 192.168.1.123，用这个域名申请免费的https通配符证书，[在线免费申请（ZeroSSL、Let’s Encrypt）](https://xiangyuecn.github.io/ACME-HTML-Web-Browser-Client/ACME-HTML-Web-Browser-Client.html) 


## 可用的http接口
http接口在`server-http.js`文件内实现，文件开头有测试代码。
``` js
接口统一返回结果：
{
    c:0 //code，0正常，其他错误
    ,m:"" //message，错误消息
    ,v:any //value，返回的结果值，一般是对象
}


/upload    使用上传文件表单上传，POST multipart/form-data
    参数：upfile 上传的文件
    返回：{ url:"文件访问url" }
/uploadBase64    使用普通表单上传，POST application/x-www-form-urlencoded
    参数：upfile_b64 上传的文件base64字符串，mime 文件类型
    返回：{ url:"文件访问url" }

/Recorder/***    使用GET请求访问Recorder目录内的静态文件
```


## 可用的websocket接口
ws接口在`server-websocket.js`文件内实现，文件开头有测试代码。
```
/* Detailed source-code truncated for AI context efficiency. */
```

---

### Overview (`assets/npm-home/README.md`)

# Recorder：recorder-core 用于html5录音

GitHub: [https://github.com/xiangyuecn/Recorder](https://github.com/xiangyuecn/Recorder)

Gitee: [https://gitee.com/xiangyuecn/Recorder](https://gitee.com/xiangyuecn/Recorder)

文档和详细使用方法请参考上面两个Recorder仓库。npm recorder这个名字已被使用，因此在Recorder基础上增加后缀-core，就命名为recorder-core，和Recorder核心文件同名。


# 如何使用

## 使用npm安装
```
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目录，进行提交

```
//登录，注意一定要使用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@@

---

### Overview (`assets/sonic-java/README.md`)

# Sonic Java版测试

Sonic是一个音频变速变调的开源库，[waywardgeek/sonic](https://github.com/waywardgeek/sonic)

Sonic.java已移植为sonic.js，并且做了精简，代码在[extensions/sonic.js](https://github.com/xiangyuecn/Recorder/blob/master/src/extensions/sonic.js)


## 测试方法

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

---

### Overview (`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端有配套的[原生录音插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin)可供搭配使用，兼容性和体验更好（暂无鸿蒙原生）

**详细文档(含Demo项目)：** [https://github.com/xiangyuecn/Recorder/tree/master/app-support-sample/demo_UniApp](https://github.com/xiangyuecn/Recorder/tree/master/app-support-sample/demo_UniApp)

**Recorder开源库地址：** [https://github.com/xiangyuecn/Recorder](https://github.com/xiangyuecn/Recorder)

如果github打不开，可以[点此访问Gitee仓库地址](https://gitee.com/xiangyuecn/Recorder/tree/master/app-support-sample/demo_UniApp) 。


*⠀*

## 测试方法
**示例项目如果在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

```
/* Detailed source-code truncated for AI context efficiency. */
```

5. 编译成app时，默认需要额外提供一个renderjs模块，请照抄下面这段代码放到vue文件末尾

``` html

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

```


*⠀*

*⠀*

## 二、调用录音
```
/* Detailed source-code truncated for AI context efficiency. */
```






*⠀*

*⠀*

*⠀*

*⠀*

# 录音权限配置、需要注意的细节
## 编译成App时录音和权限
编译成App录音时，需要在uni-app项目的 `manifest.json` 中配置好Android、iOS、鸿蒙的录音权限声明：
```
//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中同样会存在；使用配套的[原生录音插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin)或uts插件时无以上问题和版本限制（uts插件开发中暂不可用），Android也无以上问题。

当App中音频编码是在renderjs中进行处理时，录音结束后会将整个录音文件传回逻辑层，由于uni-app的逻辑层和renderjs层大点的文件传输会比较慢，**建议Start时使用takeoffEncodeChunk实时获取音频文件数据可避免Stop时产生超大数据回传**；配置了`RecordApp.UniWithoutAppRenderjs=true`后，因为音频编码直接是在逻辑层中进行，将不存在传输性能损耗，但会影响逻辑层的性能（正常情况轻微不明显），需要配套使用原生录音插件才可以进行此项配置（暂无鸿蒙原生）。


*⠀*

## 编译成微信小程序时录音和权限
编译成微信小程序时，录音功能由小程序的`RecorderManager`提供，屏蔽了微信原有的底层细节（无录音时长限制）。

小程序录音需要用户授予录音权限，调用`RecordApp.RequestPermission`的时候会检查是否能正常录音，如果用户拒绝了录音权限，会进入错误回调，回调里面你应当编写代码检查`wx.getSetting`中的`scope.record`录音权限，然后引导用户进行授权（可调用`wx.openSetting`打开设置页面，方便用户给权限）。

**注意：上架小程序需要到小程序管理后台《[用户隐私保护指引](https://developers.weixin.qq.com/miniprogram/dev/framework/user-privacy/miniprogram-intro.html)》中声明录音权限，否则正式版将无法调用录音功能（请求权限时会直接走错误回调）。**

更多细节请参考 [miniProgram-wx](https://gitee.com/xiangyuecn/Recorder/tree/master/app-support-sample/miniProgram-wx) 测试项目文档。


*⠀*

## 编译成H5时录音和权限
编译成H5时，录音功能由Recorder H5提供，无需额外处理录音权限。



*⠀*

## PCM音频流式播放、语音通话、回声消除、声音外放
在App、H5中，均可使用H5版的[BufferStreamPlayer](https://gitee.com/xiangyuecn/Recorder/blob/master/src/extensions/buffer_stream.player.js)来实时流式播放语音；其中App中需要在renderjs中加载BufferStreamPlayer，在逻辑层中调用`RecordApp.UniWebViewVueCall`等方法将逻辑层中接收到的实时语音数据发送到renderjs中播放；播放声音的同时进行录音，声音可能会被录进去产生回声，因此一般需要打开回声消除；调用代码参考demo中的[test_realtime_voice.vue](https://gitee.com/xiangyuecn/Recorder/blob/master/app-support-sample/demo_UniApp/pages/recTest/test_realtime_voice.vue)。

App中如果搭配使用了配套的[原生录音插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin)，可以调用原生实现的PcmPlayer播放器实时流式播放PCM音频，边录音边播放更流畅；同时也支持完整播放，比如AI语音合成的播放；调用代码参考demo中的[test_player_nativePlugin_pcmPlayer.vue](https://gitee.com/xiangyuecn/Recorder/blob/master/app-support-sample/demo_UniApp/pages/recTest/test_player_nativePlugin_pcmPlayer.vue)。

微信小程序请参考 [miniProgram-wx](https://gitee.com/xiangyuecn/Recorder/tree/master/app-support-sample/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文档](https://gitee.com/xiangyuecn/Recorder/tree/master/app-support-sample/demo_UniApp)，含Demo项目；更高级使用还需深入阅读 [Recorder文档](https://gitee.com/xiangyuecn/Recorder)、[RecordApp文档](https://gitee.com/xiangyuecn/Recorder/tree/master/app-support-sample) （均为完整的一个README.md文档），Recorder文档中包含了更丰富的示例代码：基础录音、实时处理、格式转码、音频分析、音频混音、音频生成 等等，大部分能在uniapp中直接使用。




*⠀*

*⠀*

*⠀*

# 本组件的授权许可限制
**本组件内的app-uni-support.js文件在uni-app中编译到App平台时仅供测试用（App平台包括：Android App、iOS App、鸿蒙App），不可用于正式发布或商用，正式发布或商用需先到DCloud插件市场购买[此带授权的插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin-Android)（费用为¥199元，赠送Android版原生插件），即可获得授权许可**；插件市场支持开票，具体请参考 [DCloud 如何开具发票](https://uniapp.dcloud.net.cn/dev/finance/invoice.html) ；编译到其他平台时无此授权限制，比如：H5、小程序，均为免费授权。

在App中，如果未获得授权许可，将会在App打开后第一次调用`RecordApp.RequestPermission`请求录音权限时，弹出“未获得商用授权时，App上仅供测试”提示框。

在DCloud插件市场购买了[带授权的插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin-Android)获得了授权后，请在调用`RecordApp.RequestPermission`请求录音权限前，赋值`RecordApp.UniAppUseLicense="我已获得UniAppID=***的商用授权"`（星号为你项目的uni-app应用标识），就不会弹提示框了；或者直接使用配套的[原生录音插件](https://ext.dcloud.net.cn/plugin?name=Recorder-NativePlugin)，设置`RecordApp.UniNativeUtsPlugin`参数后，也不会弹提示框；其他情况请联系作者咨询，更多细节请参考[本组件的GitHub文档](https://gitee.com/xiangyuecn/Recorder/tree/master/app-support-sample/demo_UniApp)。

获取授权、需要技术支持、或有不清楚的地方，可以联系客服：本插件页面`进入交流群`，或`QQ(` `125` `165` `4593` `)` ，或者直接联系作者`QQ(` `753` `610` `399` `)` （回复可能没有客服及时）。

插件开发维护不易，感谢支持~


*⠀*

*⠀*

---

### Overview (`app-support-sample/demo_UniApp/uni_modules/Recorder-UniHarmony/readme.md`)

# 使用方法文档
本UTS插件为[跨平台Recorder录音插件Recorder-UniCore](https://ext.dcloud.net.cn/plugin?name=Recorder-UniCore)的配套组件，提供鸿蒙App下的权限请求、文件保存原生功能。

插件默认配置了 `麦克风` 权限，对应权限声明为 `正在申请使用麦克风权限，以便为您提供相应的服务`，也可用于更多权限的声明和请求；请参考下面的权限请求方法，可以到json5配置文件中自行添加修改。

## import
``` js
import { Harmony_RequestPermissionAsync, Harmony_SaveLocalFile } from '@/uni_modules/Recorder-UniHarmony';
```

## Harmony_RequestPermissionAsync(permissions : Array<string>)
请求权限，参数有几个权限，异步返回的结果对应的就有几个值。非鸿蒙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" 返回保存的完整路径
```

---

--- METRICS ---
- Files Extracted: 13
- Estimated Token Budget: ~27800 tokens
- Canonical Reference: https://codewiki.google/github.com/xiangyuecn/Recorder
