# uni.getRecorderManager()

录音管理

# getRecorderManager 兼容性

Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 4.61 4.61 4.61 4.61

web平台可通过插件拉齐,详见

# 返回值

类型
RecorderManager

# RecorderManager 的方法

# start(options : RecorderManagerStartOptions) : void;

start 开始录音

# start 兼容性
Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 4.61 4.61 4.61 4.61
# 参数
名称 类型 必填 默认值 兼容性 描述
options RecorderManagerStartOptions
名称 类型 必备 默认值 兼容性 描述
duration number
指定录音的时长,单位 ms ,如果传入了合法的 duration ,在到达指定的 duration 后会自动停止录音,默认值 60000(1 分钟),微信小程序最大值 600000(10 分钟), App 平台没有最大值限制
sampleRate number
采样率,有效值 8000/16000/44100, Android平台默认是8000,iOS平台默认是44100
numberOfChannels number 2
录音通道数,有效值 1/2
encodeBitRate number 48000
编码码率,有效值见下表格
format string aac
音频格式
合法值 兼容性 描述
aac
aac格式
mp3
mp3格式
pcm
pcm格式
wav
wav格式
m4a
m4a格式
frameSize number
指定帧大小,单位 KB。传入 frameSize 后,每录制指定帧大小的内容后,会回调录制的文件内容,不指定则不会回调。暂仅支持 mp3 格式。

# pause() : void;

pause 暂停录音,App-Android平台在Android 7.0及以后版本支持

# pause 兼容性
Web 微信小程序 Android 系统版本 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 7.0 4.61 4.61 4.61 4.61

# resume() : void;

resume 继续录音,App-Android平台在Android 7.0及以后版本支持

# resume 兼容性
Web 微信小程序 Android 系统版本 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 7.0 4.61 4.61 4.61 4.61

# stop() : void;

stop 停止录音

# stop 兼容性
Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 4.61 4.61 4.61 4.61

# onStart(options : (result : any) => void) : void;

onStart 录音开始事件

# onStart 兼容性
Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 4.61 4.61 4.61 4.61
# 参数
名称 类型 必填 默认值 兼容性 描述
options (result: any) => void

# offStart() : void;

offStart 取消监听录音开始事件

# offStart 兼容性
Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 5.0 5.0 5.0 5.0

# onPause(options : (result : any) => void) : void;

onPause 录音暂停事件,App-Android平台在Android 7.0及以后版本支持

# onPause 兼容性
Web 微信小程序 Android 系统版本 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 7.0 4.61 4.61 4.61 4.61
# 参数
名称 类型 必填 默认值 兼容性 描述
options (result: any) => void

# offPause() : void;

offPause 取消监听录音暂停事件

# offPause 兼容性
Web 微信小程序 Android 系统版本 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 7.0 5.0 5.0 5.0 5.0

# onStop(options : (result : RecorderManagerOnStopResult) => void) : void;

onStop 录音停止事件,会回调文件地址

# onStop 兼容性
Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 4.61 4.61 4.61 4.61
# 参数
名称 类型 必填 默认值 兼容性 描述
options (result: RecorderManagerOnStopResult) => void
# RecorderManagerOnStopResult 的属性值
名称 类型 必备 默认值 兼容性 描述
tempFilePath string
录音文件的临时路径

# offStop() : void;

offStop 取消监听录音停止事件

# offStop 兼容性
Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 5.0 5.0 5.0 5.0

# onFrameRecorded(options : (result : any) => void) : void;

onFrameRecorded 已录制完指定帧大小的文件,会回调录音分片结果数据。如果设置了 frameSize ,则会回调此事件

# onFrameRecorded 兼容性
Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 x x x x
# 参数
名称 类型 必填 默认值 兼容性 描述
options (result: any) => void

# offFrameRecorded() : void;

offFrameRecorded 取消监听帧回调事件

# offFrameRecorded 兼容性
Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 x x x x

# onError(options: (result: IRecorderManagerFail) => void): void;

onError 录音错误事件, 会回调错误信息

# onError 兼容性
Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 4.61 4.61 4.61 4.61
# 参数
名称 类型 必填 默认值 兼容性 描述
options (result: IRecorderManagerFail) => void
# IRecorderManagerFail 的属性值
名称 类型 必备 默认值 兼容性 描述
errCode number
错误码
合法值 兼容性 描述
1107601
没有麦克风权限
1107602
不支持该采样率
1107603
采样率是和编码码率不匹配
1107604
启动失败
1107605
不支持该音频格式
1107606
其他错误
1107607
被打断
1107608
正在录音中,请稍后执行此操作
errSubject string
统一错误主题(模块)名称
data any
错误信息中包含的数据
cause Error 源错误信息,可以包含多个错误,详见SourceError
errMsg string

# offError(): void;

offError 取消监听录音错误事件

# offError 兼容性
Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 5.0 5.0 5.0 5.0

# onResume(options : (result : any) => void) : void;

onResume 监听录音继续事件,App-Android平台在Android 7.0及以后版本支持

# onResume 兼容性
Web 微信小程序 Android 系统版本 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 7.0 4.61 4.61 4.61 4.61
# 参数
名称 类型 必填 默认值 兼容性 描述
options (result: any) => void

# offResume() : void;

offResume 取消监听录音继续事件

# offResume 兼容性
Web 微信小程序 Android 系统版本 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 7.0 5.0 5.0 5.0 5.0

# onInterruptionBegin(options : (result : any) => void) : void;

onInterruptionBegin 监听录音因为受到系统占用而被中断开始事件

# onInterruptionBegin 兼容性
Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 4.61 4.61 4.61 4.61
# 参数
名称 类型 必填 默认值 兼容性 描述
options (result: any) => void

# offInterruptionBegin() : void;

offInterruptionBegin 取消监听录音因为受到系统占用而被中断开始事件

# offInterruptionBegin 兼容性
Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 5.0 5.0 5.0 5.0

# onInterruptionEnd(options : (result : any) => void) : void;

onInterruptionEnd 监听录音中断结束事件

# onInterruptionEnd 兼容性
Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 4.61 4.61 4.61 4.61
# 参数
名称 类型 必填 默认值 兼容性 描述
options (result: any) => void

# offInterruptionEnd() : void;

offInterruptionEnd 取消监听录音中断结束事件

# offInterruptionEnd 兼容性
Web 微信小程序 Android iOS iOS uni-app x UTS 插件 HarmonyOS
x 4.41 5.0 5.0 5.0 5.0

编码格式与采样率、码率的关系

Android、iOS、微信小程序

采样率 编码码率
8000 16000 - 48000
11025 16000 - 48000
12000 24000 - 64000
16000 24000 - 96000
22050 32000 - 128000
24000 32000 - 128000
32000 48000 - 192000
44100 64000 - 320000
48000 64000 - 320000

HarmonyOS

  • aac 编码格式支持码率范围[32000 - 500000]
  • mp 编码格式支持码率范围[8000, 16000, 32000, 40000, 48000, 56000, 64000, 80000, 96000, 112000, 128000, 160000, 192000, 224000, 256000, 320000]
    • 采样率使用16K以下时,对应码率范围为[8000 - 64000]
    • 采样率使用16K~32K时对应的码率范围为[8000 - 160000]
    • 采样率使用32K以上时对应的码率范围为[32000 - 320000]
  • wav 编码格式时,补丁码率 8000,采样率 64000,通道数 1

# 示例

示例为hello uni-app x alpha分支,与最新HBuilderX Alpha版同步。与最新正式版同步的master分支示例另见

该 API 不支持 Web,请运行 hello uni-app x 到 App 平台体验

扫码体验(手机浏览器跳转到App直达页)
<template>
  <page-head :title="title"></page-head>
  <view class="page-body-time">
    <text class="time-big">{{formatedRecordTime}}</text>
    <!-- <text class="time-big">{{recordTimeInterval}}</text> -->
  </view>

  <scroll-view style="flex: 1;">
    <view>
      <button class="btnstyle" size="default" @click="registerOnStart">注册onStart</button>
      <button class="btnstyle" size="default" @click="registeronStop">注册onStop</button>
      <button class="btnstyle" size="default" id="btn-error" @click="registeronError">注册onError</button>
      <button class="btnstyle" size="default" @click="registeronPause">注册onPause</button>
      <button class="btnstyle" size="default" @click="registeronResume">注册onResume</button>
      <button class="btnstyle" size="default" @click="registeronInterruptionBegin">注册onInterruptionBegin</button>
      <button class="btnstyle" size="default" @click="registeronInterruptionEnd">注册onInterruptionEnd</button>
      <view class="uni-list">
        <text style="margin-bottom: 10px"> 请选择录音格式:</text>
        <radio-group class="uni-row" @change="radioChange" style="flex-wrap: wrap">
          <radio class="uni-list-cell" style="margin-right: 15px" v-for="(item, index) in items" :key="item.value"
            :value="item.value" :checked="index === current">
            {{ item.name }}
          </radio>
        </radio-group>
      </view>
      <button class="btnstyle" size="default" :disabled="disableStartBtn" id="btn-startRecord"
        @click="startRecord()">开始录制</button>
      <button class="btnstyle" size="default" :disabled="disablePauseBtn" @click="pauseRecord">暂停录制</button>
      <button class="btnstyle" size="default" :disabled="disableResumeBtn" @click="resumeRecord">继续录制</button>
      <button class="btnstyle" size="default" id="btn-stopRecord" @click="stopRecord">停止录制</button>
      <button class="btnstyle" size="default" id="btn-startPlay" @click="playVoice">开始播放</button>
      <button class="btnstyle" size="default" id="btn-stopPlay" @click="stopVoice">停止播放</button>
    </view>

    <bottom-safe-area />
  </scroll-view>
</template>
<script setup lang="uts">
  export type ItemType = { value : string, name : string }

	type DataType = {
		registerError: boolean,
		recording: boolean,
		playing: boolean
	}

	const disableStartBtn = ref(false)
	const disableResumeBtn = ref(false)
	const disablePauseBtn = ref(false)
	const title = ref('start/stopRecord、play/stopVoice')
	const hasRecord = ref(false) //是否有了一个
	// 使用reactive避免ref数据在自动化测试中无法访问
	const data = reactive({
		registerError: false,
		recording: false, //录音中
		playing: false //播放中
	} as DataType)

  const playTimeInterval = ref(0)
  const recordTimeInterval = ref(0)
  const tempFilePath = ref('')
  const recordTime = ref(0)
  const current = ref(0)
  const playTime = ref(0)
  const formatedRecordTime = ref('00:00:00') //录音的总时间
  const formatedPlayTime = ref('00:00:00')//播放录音的当前时间,
  const recorderManager = ref(null as RecorderManager | null)
  const music = ref(null as InnerAudioContext | null)
  const items = ref([
    {
      value: 'aac',
      name: 'aac'
    },
    {
      value: 'mp3',
      name: 'mp3'
    },
    {
      value: 'wav',
      name: 'wav'
    },
    // #ifdef APP-HARMONY
    {
      value: 'm4a',
      name: 'm4a'
    },
    // #endif
    // #ifndef APP-HARMONY
    {
      value: 'pcm',
      name: 'pcm'
    }
    // #endif
  ] as ItemType[])

  const radioChange = (e: UniRadioGroupChangeEvent) => {
    for (let i = 0; i < items.value.length; i++) {
      if (items.value[i].value === e.detail.value) {
        current.value = i;
        break;
      }
    }
  }

  const formatTime = (time: number): string => {
    if (typeof time !== 'number' || time < 0) {
      return time.toString()
    }

    var hour = parseInt((time / 3600).toString())
    time = time % 3600
    var minute = parseInt((time / 60).toString())
    time = time % 60
    var second = time
    return [hour, minute, second].map((n: number) => {
      let str = n.toString();
      return str.length > 1 ? str : "0" + str;
    }).join(":");
  }

  const registerOnStart = () => {
    uni.showToast({
      title: 'already registerOnStart'
    })

    recorderManager.value!.onStart(() => {
      console.log('recorder on start');
      recordTime.value = 0
      data.recording = true;
      recordTimeInterval.value = setInterval(() => {
        recordTime.value += 1;
        formatedRecordTime.value = formatTime(recordTime.value);
      }, 1000)
    });
  }

  const registeronStop = () => {
    uni.showToast({
      title: 'already registeronStop'
    })
    recorderManager.value!.onStop((res) => {
      console.log('on stop', res);
      music.value!.src = res.tempFilePath
      clearInterval(recordTimeInterval.value)
      hasRecord.value = true;
      data.recording = false;
    });
  }

  const registeronError = () => {
    uni.showToast({
      title: 'already registeronError'
    })
    data.registerError = true
    recorderManager.value!.onError((res) => {
      console.log('recorder onError', JSON.stringify(res));
    });
  }

  const registeronPause = () => {
    uni.showToast({
      title: 'already registeronPause'
    })
    recorderManager.value?.onPause(() => {
      console.log('recorder onPause');
    })
  }

  const registeronResume = () => {
    uni.showToast({
      title: 'already registeronStop'
    })
    recorderManager.value?.onResume(() => {
      console.log('recorder onResume');
    })
  }

  const registeronOnFrameRecorded = () => {
    uni.showToast({
      title: 'already registeronOnFrameRecorded'
    })
    recorderManager.value?.onFrameRecorded((res) => {
      console.log('recorder onFrameRecorded----', res);
    })
  }

  const registeronInterruptionBegin = () => {
    uni.showToast({
      title: 'already registeronInterruptionBegin'
    })
    recorderManager.value?.onInterruptionBegin(() => {
      console.log('recorder onInterruptionBegin');
    })
  }

  const registeronInterruptionEnd = () => {
    uni.showToast({
      title: 'already registeronInterruptionEnd'
    })
    recorderManager.value?.onInterruptionBegin(() => {
      console.log('recorder registeronInterruptionEnd');
    })
  }

  const pauseRecord = () => {
    console.log('recorder pause');
    recorderManager.value?.pause()
    if (data.recording) {
      disableStartBtn.value = false
      disablePauseBtn.value = true
      disableResumeBtn.value = false
    }
    clearInterval(recordTimeInterval.value)
  }

  const resumeRecord = () => {
    console.log('recorder resume ', recorderManager.value);
    recorderManager.value?.resume()
    recorderManager.value?.onResume(() => {
      console.log('recorder onResume');
    })
    if (data.recording) {
      disableStartBtn.value = false
      disablePauseBtn.value = false
      disableResumeBtn.value = true
      recordTimeInterval.value = setInterval(() => {
        recordTime.value += 1;
        formatedRecordTime.value = formatTime(recordTime.value);
      }, 1000)
    }
  }

  const startRecord = () => { //开始录音
    if (data.recording) {
      uni.showToast({
        title: disablePauseBtn.value ? "当前是录音暂停状态" : "当前正在录音"
      })
      return
    }

    console.log('startRecord', items.value[current.value].value)
    // TODO ios 在没有请求过权限之前无法得知是否有相关权限,这种状态下需要直接调用录音,但没有状态或回调判断用户拒绝
    recorderManager.value?.start({
      format: items.value[current.value].value,
      sampleRate: 8000,
      numberOfChannels: 2,
      encodeBitRate: 48000,
      frameSize: 2
    });
  }

  const stopRecord = () => { //停止录音
    recorderManager.value?.stop();
    disableStartBtn.value = false
    disablePauseBtn.value = false
    disableResumeBtn.value = false
  }

  const playVoice = () => {
    if (data.recording) {
      uni.showToast({
        title: "当前录音还未结束"
      })
      return
    }
    console.log('play voice');
    if (data.playing) {
      return
    }
    data.playing = true;
    playTimeInterval.value = setInterval(() => {
      if (playTime.value < recordTime.value) {
        playTime.value += 1;
      }
      formatedRecordTime.value = formatTime(playTime.value);
    }, 1000)
    music.value?.play();
  }

  const stopVoice = () => {
    if (data.recording) {
      uni.showToast({
        title: "当前录音还未结束"
      })
      return
    }
    clearInterval(playTimeInterval.value)
    data.playing = false;
    formatedRecordTime.value = formatTime(0);
    playTime.value = 0;
    music.value?.stop();
  }

  const end = () => {
    music.value?.stop();
    music.value?.destroy();
    // #ifdef APP
    recorderManager.value?.offError()
    recorderManager.value?.offFrameRecorded()
    recorderManager.value?.offInterruptionBegin()
    recorderManager.value?.offInterruptionEnd()
    recorderManager.value?.offPause()
    recorderManager.value?.offResume()
    recorderManager.value?.offStart()
    recorderManager.value?.offStop()
    // #endif
    recorderManager.value?.stop();
    clearInterval(recordTimeInterval.value)
    clearInterval(playTimeInterval.value);
    data.recording = false
    data.playing = false
    hasRecord.value = false;
    playTime.value = 0
    recordTime.value = 0;
    formatedRecordTime.value = "00:00:00"
    formatedRecordTime.value = "00:00:00";
  }

  const clear = () => {
    end();
  }

  onUnload(() => {
    end();
  })

  onLoad(() => {
    music.value = uni.createInnerAudioContext();
    music.value!.onEnded(() => {
      clearInterval(playTimeInterval.value)
      var playTimeValue = 0
      console.log('play voice finished')
      data.playing = false;
      formatedPlayTime.value = formatTime(playTimeValue);
      playTime.value = playTimeValue;
    });
    recorderManager.value = uni.getRecorderManager();

    recorderManager.value!.onStart(() => {
      console.log('recorder onStart');
      disableStartBtn.value = true
      disablePauseBtn.value = false
      disableResumeBtn.value = false
      data.recording = true;
      recordTime.value = 0
      recordTimeInterval.value = setInterval(() => {
        recordTime.value += 1;
        formatedRecordTime.value = formatTime(recordTime.value);
      }, 1000)
    });
    recorderManager.value!.onStop((res) => {
      console.log('on stop', res.tempFilePath);
      disablePauseBtn.value = false
      disableResumeBtn.value = false
      disableStartBtn.value = false
      music.value!.src = res.tempFilePath
      clearInterval(recordTimeInterval.value)
      hasRecord.value = true;
      data.recording = false;
    });
    recorderManager.value!.onError((res) => {
      console.log('recorder onError', JSON.stringify(res));
      disablePauseBtn.value = false
      disableResumeBtn.value = false
      disableStartBtn.value = false
      data.registerError = true
      uni.showToast({
        title: JSON.stringify(res)
      })
    });
  })

	defineExpose({
		data
	})
</script>

<style>
  .page-body-time {
    display: flex;
    flex-direction: column;
    align-items: center;
  }

  .time-big {
    font-size: 30px;
    margin: 10px;
  }

  .btnstyle {
    margin-left: 30px;
    margin-right: 30px;
    margin-top: 10px;
  }

  .uni-list {
    border-bottom: 0px;
    background-color: transparent;
    margin-left: 30px;
    margin-right: 30px;
    margin-top: 10px;
    margin-bottom: 10px;
  }
</style>

# 参见

# 注意

  • 鸿蒙平台权限设置 此接口需要权限 ohos.permission.MICROPHONE。具体配置方法请参考鸿蒙权限配置指南

# 通用类型

# GeneralCallbackResult

名称 类型 必备 默认值 兼容性 描述
errMsg string
错误信息