更新记录

1.3.2(2026-04-21)

修复 iOS 云打包时 UTS 插件编译失败问题(Optional 回调未解包)

1.3.1(2026-04-03)

修复IOS在调用stop方法的时候,概率性阻塞卡死的问题

1.3.0(2026-03-31)

  • 新增声音源配置,支持设置是否隔离设备扬声器声音,仅录取外部麦克风声音
  • 新增录音是否与其他App音频共存的配置
查看更多

平台兼容性

uni-app(4.0)

Vue2 Vue3 Chrome Safari app-vue app-nvue Android Android插件版本 iOS iOS插件版本 鸿蒙
- - - - - - 5.0 1.0.0 12 1.0.0 -
微信小程序 支付宝小程序 抖音小程序 百度小程序 快手小程序 京东小程序 鸿蒙元服务 QQ小程序 飞书小程序 小红书小程序 快应用-华为 快应用-联盟
- - - - - - - - - - - -

uni-app x(5.0)

Chrome Safari Android Android插件版本 iOS iOS插件版本 鸿蒙 微信小程序
- - 5.0 1.0.0 12 1.0.0 - -

其他

多语言 暗黑模式 宽屏模式

Android iOS录音管理器 - 实时PCM音频流录音插件

uni-app Platform

支持实时PCM帧回调的uni-app录音插件,提供流式音频数据处理能力,适用于语音识别、实时转写、波形显示等场景。纯UTS实现,Android/iOS双端适配。

✨ 核心特性

  • 实时PCM音频流 - 边录边获取原始PCM数据,延迟低至40ms
  • 实时分贝监测 - RMS算法计算,实时返回音量分贝值
  • 流式数据处理 - 支持边录边传、边录边识别等流式场景
  • WAV文件保存 - 自动保存为标准WAV格式,支持file://直接上传
  • Android/iOS - 双端适配,统一API,透明处理平台差异
  • 暂停/恢复 - 支持录音过程中暂停和恢复
  • 录音与播放共存 - 录音期间可同时播放音频,互不干扰
  • 录音源可控(麦克风/混音模式) - 支持仅录制麦克风声音,或开启混音模式录制“麦克风 + 应用内播放音频”
  • 中断自动检测 - 多层看门狗机制,来电/麦克风被抢占时立即回调通知
  • 突破录音时长限制 - 无系统默认60秒/10分钟限制,支持长时间持续录音
  • 纯UTS实现 - 无需原生代码,开箱即用

🔐 权限说明

Android

插件自动声明以下权限:

权限 用途 必须
android.permission.RECORD_AUDIO 录音 ✅ 必须
android.permission.POST_NOTIFICATIONS 录音通知(Android 13+,showNotification=true 时使用) 可选
android.permission.READ_PHONE_STATE 来电检测,来电时自动中断录音 可选

说明READ_PHONE_STATE 无需主动申请。已授权时插件自动启用来电检测;未授权时静默降级,录音功能不受影响,仅来电中断检测延迟约3秒。

iOS

需要在 Info.plist 中添加麦克风权限说明(插件已自动配置):

<key>NSMicrophoneUsageDescription</key>
<string>需要使用麦克风进行录音</string>

后台录音(可选):若需要应用进入后台后继续录音,需在 manifest.json 中配置:

"app-plus": {
  "distribute": {
    "ios": {
      "UIBackgroundModes": ["audio"]
    }
  }
}

未配置时,进入后台约3秒后 onError 会收到 8010006 错误回调(系统中断),录音自动停止。

🚀 快速开始

基本使用

import { getRecorderManager } from '@/uni_modules/dh-recorder';

// 获取录音管理器实例(单例)
const recorderManager = getRecorderManager();

// 监听录音开始
recorderManager.onStart(() => {
  console.log('录音开始');
});

// 监听录音帧数据(核心功能)
recorderManager.onFrameRecorded((res) => {
  // Android: res.base64 为 base64 字符串,需解码
  // iOS:     res.frameBuffer 为 ArrayBuffer,可直接使用
  const arrayBuffer = res.frameBuffer ?? uni.base64ToArrayBuffer(res.base64 ?? '');

  console.log('帧数据大小:', arrayBuffer.byteLength); // 如: 1280字节
  console.log('当前分贝:', res.decibel); // 如: -35.2 dB
});

// 监听录音停止
recorderManager.onStop((res) => {
  console.log('📁 录音文件:', res.tempFilePath); // file://格式路径
  console.log('⏱️  录音时长:', res.duration, 'ms');
  console.log('📦 文件大小:', res.fileSize, 'bytes');
});

// 监听错误(含中断通知)
recorderManager.onError((err) => {
  console.error('❌ 录音错误:', err.errCode, err.errMsg);
  // 8010005: 麦克风被其他应用抢占
  // 8010006: 来电/系统中断
});

// 开始录音
recorderManager.start({
  sampleRate: 16000, // 采样率:16kHz(推荐语音场景)
  numberOfChannels: 1, // 声道数:单声道
  bitsPerSample: 16, // 位深:16位
  frameSize: 640, // 帧大小:640样本 ≈ 40ms @ 16kHz
  format: 'wav', // 格式:WAV
  showNotification: true, // 仅Android:开启录音通知,增强后台/息屏存活率
  notificationName: '我的App',
  notificationText: '正在录音中,不要关闭应用',
  androidOutputScope: 'external',
  isolateSpeaker: false, // 仅Android:是否只录取麦克风声音(隔离扬声器),默认false
  mixWithOthers: true, // 录音是否与其他App音频共存,默认true
});

// 停止录音
recorderManager.stop();

上传录音文件

recorderManager.onStop((res) => {
  if (res.success && res.tempFilePath) {
    uni.uploadFile({
      url: 'https://your-server.com/upload',
      filePath: res.tempFilePath, // file://格式,可直接上传
      name: 'audioFile',
      formData: {
        duration: res.duration,
        fileSize: res.fileSize,
      },
      success: (uploadRes) => {
        console.log('✅ 上传成功:', uploadRes.data);
      },
      fail: (err) => {
        console.error('❌ 上传失败:', err);
      },
    });
  }
});

暂停和恢复

recorderManager.onPause(() => {
  console.log('⏸️ 录音已暂停');
});
recorderManager.onResume(() => {
  console.log('▶️ 录音已恢复');
});

recorderManager.pause(); // 暂停
recorderManager.resume(); // 恢复
recorderManager.cancel(); // 取消(不保存文件)

中断处理

中断发生时,onError 先回调(原因),onStop 后回调(已录文件),可根据 interrupted 字段区分:

recorderManager.onError((err) => {
  switch (err.errCode) {
    case 8010005:
      showToast('录音被中断,请重新开始');
      break;
    case 8010006:
      showToast('录音被电话中断');
      break;
    default:
      showToast('录音出错:' + err.errMsg);
  }
});

recorderManager.onStop((res) => {
  if (res.interrupted) {
    // 外部中断自动停止:保存已录片段或提示用户
    console.log('中断前已录时长:', res.duration, 'ms');
    console.log('已保存部分录音:', res.tempFilePath);
    // 可选:上传已录片段
    uploadPartialRecording(res.tempFilePath);
  } else {
    // 用户主动停止:正常流程
    uploadRecording(res.tempFilePath);
  }
  updateUIToStopped();
});

📖 API文档

getRecorderManager()

获取录音管理器单例。

返回值: IRecorderManager

RecorderManager 实例方法

start(options)

开始录音。若已在录音中,会先停止当前录音再重新开始。

参数:

参数名 类型 必填 默认值 说明
sampleRate number 16000 采样率(Hz),常用值:8000/16000/44100/48000
numberOfChannels number 1 声道数:1=单声道,2=立体声
bitsPerSample number 16 位深,支持:8/16/24/32
frameSize number 640 帧大小(样本数)。16位时:640样本=1280字节≈40ms@16kHz
format string 'wav' 录音格式:wavpcm
showNotification boolean false 仅Android:显示常驻通知,增强后台/息屏存活率
notificationName string 应用名称 仅Android:通知标题
notificationText string 正在录音中,不要关闭应用 仅Android:通知内容
androidOutputScope string 'external' 仅Android:文件输出目录,external(外部应用缓存)或 internal(内部私有)
isolateSpeaker boolean false 仅Android有效:录音时隔离设备扬声器声音,只录取外部(麦克风)声音,不录入本机播放的音频
mixWithOthers boolean true 录音是否与其他App音频共存。true(默认):录音期间背景音乐/视频等继续播放;false:录音独占音频,其他App音频会暂停。Android需API 26+生效,iOS均支持

帧大小(frameSize)说明:

  • 单位是样本数,不是字节数
  • 16位PCM:1样本 = 2字节,640样本 = 1280字节 ≈ 40ms @ 16kHz
  • 值越小回调频率越高,实时性越好,CPU占用也越高

后台录音增强(Android):

  • showNotification=false(默认):不显示通知,后台/息屏存活率取决于系统
  • showNotification=true:显示常驻通知,可提升后台/息屏场景下的录音存活率

文件目录说明(Android):

  • external(默认):外部应用专属缓存目录,更利于本地 file:// 直接播放
  • internal:内部私有缓存目录,安全性更高
  • 外部目录不可用时自动回退到内部

stop()

停止录音,触发 onStop 回调,返回录音文件信息。

pause()

暂停录音,触发 onPause 回调。暂停期间音频帧不回调,但底层持续读取以防缓冲区积压。

resume()

恢复录音,触发 onResume 回调。

cancel()

取消录音,不保存文件,不触发 onStop 回调。

onStart(callback) / offStart(callback)

监听/取消监听录音开始事件。

onStop(callback) / offStop(callback)

监听/取消监听录音停止事件。

回调参数:

{
  success: boolean; // 是否成功
  tempFilePath: string; // 录音文件路径(file://格式)
  duration: number; // 录音总时长(毫秒)
  fileSize: number; // 文件大小(字节)
  interrupted: boolean; // 是否因外部中断(来电、麦克风被抢占等)自动停止,主动调用 stop() 时为 false
}

中断时 onError 先触发(说明原因),onStop 后触发(交付已录文件)。若中断时尚无任何录音数据(首帧未到达),则不触发 onStop

路径格式示例:

  • Android(external): file:///storage/emulated/0/Android/data/com.xxx/cache/dh_record_1234567890.wav
  • iOS: file:///var/mobile/Containers/Data/Application/.../tmp/dh_record_1234567890.wav

onFrameRecorded(callback) / offFrameRecorded(callback)

监听/取消监听录音帧数据事件,这是本插件的核心功能

回调参数:

{
  frameBuffer?: ArrayBuffer; // 音频帧数据(iOS,Int16小端序PCM)
  base64?: string;           // base64编码的音频帧数据(Android)
  isLastFrame: boolean;      // 是否为最后一帧(当前始终为 false)
  decibel: number;           // 当前帧分贝值(约-96dB静音 到 0dB满刻度)
}

跨平台差异:

  • iOS:通过 frameBuffer(ArrayBuffer)直接传输,可直接使用 DataViewInt16Array 等API
  • Android:通过 base64 传输,需用 uni.base64ToArrayBuffer(res.base64) 转换
// 兼容两端的写法
recorderManager.onFrameRecorded((res) => {
  const buffer = res.frameBuffer ?? uni.base64ToArrayBuffer(res.base64 ?? '');
  // 使用 buffer...
});

onError(callback) / offError(callback)

监听/取消监听录音错误及中断事件。

回调参数:

{
  errCode: number; // 错误码(见下表)
  errSubject: string; // 错误主题(固定为 'dh-recorder')
  errMsg: string; // 错误详细描述
}

错误码说明:

错误码 含义 典型场景
8010001 录音权限被拒绝 用户拒绝麦克风授权
8010002 录音初始化失败 硬件或引擎创建失败
8010003 录音启动失败(麦克风被占用) start() 时麦克风已被其他应用占用
8010004 录音停止失败 stop() 过程中发生异常
8010005 录音被中断 运行中麦克风被抢占、持续无音频数据
8010006 录音被系统/电话中断 来电、其他应用抢占音频会话、进入后台(iOS无后台权限时)
8010007 录音数据处理异常 帧数据读取或转换时发生内部异常

onPause(callback) / offPause(callback)

监听/取消监听录音暂停事件。

onResume(callback) / offResume(callback)

监听/取消监听录音恢复事件。

🎯 高级用法

实时语音识别

const recorderManager = getRecorderManager();
const ws = new WebSocket('wss://asr-api.example.com');

recorderManager.onFrameRecorded((res) => {
  if (ws.readyState === WebSocket.OPEN) {
    // Android 需先解码,iOS 可直接发送
    const buffer = res.frameBuffer ?? uni.base64ToArrayBuffer(res.base64 ?? '');
    ws.send(buffer);
  }
});

recorderManager.start({
  sampleRate: 16000,
  numberOfChannels: 1,
  format: 'pcm', // 实时传输用PCM,无WAV头,减少传输量
});

实时音量显示

const volumePercent = ref(0);

recorderManager.onFrameRecorded((res) => {
  // 将 -96dB ~ 0dB 映射到 0 ~ 100
  const normalized = Math.max(0, Math.min(100, ((res.decibel + 96) / 96) * 100));
  volumePercent.value = normalized;
});

录音波形可视化

const waveformData = ref<number[]>([]);

recorderManager.onFrameRecorded((res) => {
  const buffer = res.frameBuffer ?? uni.base64ToArrayBuffer(res.base64 ?? '');
  const int16Array = new Int16Array(buffer);

  // 每10个样本抽1个,归一化到[-1, 1]
  const samples: number[] = [];
  for (let i = 0; i < int16Array.length; i += 10) {
    samples.push(int16Array[i] / 32768);
  }

  // 保留最近100个点
  waveformData.value = [...waveformData.value, ...samples].slice(-100);
});

📝 注意事项

  1. 权限:首次使用会自动请求录音权限。Android 13+ 的通知权限需额外申请(通过插件提供的 requestNotificationPermission()

  2. iOS 后台录音:默认进入后台会中断录音并收到 8010006 错误。如需后台录音,在 manifest.json 中配置 UIBackgroundModes: ["audio"]

  3. 内存管理onFrameRecorded 默认约40ms回调一次,iOS 录音数据全程在内存中积累,长时间录音(建议不超过20分钟)注意内存占用

  4. 采样率

    • 语音场景:推荐 16000Hz(小文件、兼容性好)
    • 音乐场景:推荐 44100Hz 或 48000Hz
    • iOS 实际采样率由硬件决定(通常48kHz),插件自动处理,用户无需关心
  5. 帧大小

    • 语音识别推荐:640样本(40ms @ 16kHz)
    • 实时显示推荐:320样本(20ms @ 16kHz)
  6. 格式选择

    • wav:带44字节文件头,可直接播放,推荐
    • pcm:纯原始数据,适合实时传输,播放需自行添加头

🔧 兼容性

平台 支持 最低版本 核心API
Android API 21(Android 5.0) AudioRecord
iOS iOS 12.0 AVAudioEngine
Web - -
小程序 - -

💬 联系方式

如有问题或建议,欢迎通过以下方式反馈:

  • 提交 Issue
  • DCloud插件市场评论
  • DCloud插件交流群

隐私、权限声明

1. 本插件需要申请的系统权限列表:

麦克风权限

2. 本插件采集的数据、发送的服务器地址、以及数据用途说明:

插件会访问麦克风进行录音

3. 本插件是否包含广告,如包含需详细说明广告表达方式、展示频率: