# uni.getRecorderManager()

录音管理

# getRecorderManager 兼容性

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

# 返回值

类型
RecorderManager

# RecorderManager 的方法

# start(options : RecorderManagerStartOptions) : void;

开始录音

# start 兼容性
Web 微信小程序 Android iOS HarmonyOS
x 4.41 4.61 4.61 4.61
# 参数
名称 类型 必填 默认值 兼容性 描述
options RecorderManagerStartOptions - - -
名称 类型 必备 默认值 兼容性 描述
duration number -
指定录音的时长,单位 ms ,如果传入了合法的 duration ,在到达指定的 duration 后会自动停止录音,默认值 60000(1 分钟),微信小程序最大值 600000(10 分钟), App 平台没有最大值限制
sampleRate number 8000
采样率,有效值 8000/16000/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;

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

# pause 兼容性
Web 微信小程序 Android 系统版本 Android iOS HarmonyOS
x 4.41 7.0 4.61 4.61 4.61

# resume() : void;

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

# resume 兼容性
Web 微信小程序 Android 系统版本 Android iOS HarmonyOS
x 4.41 7.0 4.61 4.61 4.61

# stop() : void;

停止录音

# stop 兼容性
Web 微信小程序 Android iOS HarmonyOS
x 4.41 4.61 4.61 4.61

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

录音开始事件

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

监听录音中断结束事件

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

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

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

该 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-flex 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>
  </scroll-view>
</template>
<script>
  export type ItemType = { value : string, name : string }

  export default {
    data() {
      return {
        disableStartBtn: false,
        disableResumeBtn: false,
        disablePauseBtn: false,
        title: 'start/stopRecord、play/stopVoice',
        recording: false, //录音中
        playing: false, //播放中
        registerError: false,
        hasRecord: false, //是否有了一个
        playTimeInterval: 0,
        recordTimeInterval: 0,
        tempFilePath: '',
        recordTime: 0,
        current: 0,
        playTime: 0,
        formatedRecordTime: '00:00:00', //录音的总时间
        formatedPlayTime: '00:00:00',//播放录音的当前时间,
        recorderManager: null as RecorderManager | null,
        music: null as InnerAudioContext | null,
        items: [
          {
            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[]
      }
    },
    onUnload: function () {
      this.end();
    },
    onLoad: function () {
      this.music = uni.createInnerAudioContext();
      this.music!.onEnded(() => {
        clearInterval(this.playTimeInterval)
        var playTime = 0
        console.log('play voice finished')
        this.playing = false;
        this.formatedPlayTime = this.formatTime(playTime);
        this.playTime = playTime;
      });
      this.recorderManager = uni.getRecorderManager();

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

    methods: {
      radioChange(e : UniRadioGroupChangeEvent) {
        for (let i = 0; i < this.items.length; i++) {
          if (this.items[i].value === e.detail.value) {
            this.current = i;
            break;
          }
        }
      },
      registerOnStart() {
        uni.showToast({
          title: 'already registerOnStart'
        })

        this.recorderManager!.onStart(() => {
          console.log('recorder on start');
          this.recordTime = 0
          this.recording = true;
          this.recordTimeInterval = setInterval(() => {
            this.recordTime += 1;
            this.formatedRecordTime = this.formatTime(this.recordTime);
          }, 1000)
        });
      },
      registeronStop() {
        uni.showToast({
          title: 'already registeronStop'
        })
        this.recorderManager!.onStop((res) => {
          console.log('on stop', res.tempFilePath);
          this.music!.src = res.tempFilePath
          clearInterval(this.recordTimeInterval)
          this.hasRecord = true;
          this.recording = false;
        });
      },
      registeronError() {
        uni.showToast({
          title: 'already registeronError'
        })
        this.registerError = true
        this.recorderManager!.onError((res) => {
          console.log('recorder onError', JSON.stringify(res));
        });
      },
      registeronPause() {
        uni.showToast({
          title: 'already registeronPause'
        })
        this.recorderManager?.onPause(() => {
          console.log('recorder onPause');
        })
      },
      registeronResume() {
        uni.showToast({
          title: 'already registeronStop'
        })
        this.recorderManager?.onResume(() => {
          console.log('recorder onResume');
        })
      },
      registeronOnFrameRecorded() {
        uni.showToast({
          title: 'already registeronOnFrameRecorded'
        })
        this.recorderManager?.onFrameRecorded((res) => {
          console.log('recorder onFrameRecorded----', res);
        })
      },
      registeronInterruptionBegin() {
        uni.showToast({
          title: 'already registeronInterruptionBegin'
        })
        this.recorderManager?.onInterruptionBegin(() => {
          console.log('recorder onInterruptionBegin');
        })
      },
      registeronInterruptionEnd() {
        uni.showToast({
          title: 'already registeronInterruptionEnd'
        })
        this.recorderManager?.onInterruptionBegin(() => {
          console.log('recorder registeronInterruptionEnd');
        })
      },
      pauseRecord() {
        console.log('recorder pause');
        this.recorderManager?.pause()
        if (this.recording) {
          this.disableStartBtn = false
          this.disablePauseBtn = true
          this.disableResumeBtn = false
        }
        clearInterval(this.recordTimeInterval)
      },
      resumeRecord() {
        console.log('recorder resume ', this.recorderManager);
        this.recorderManager?.resume()
        this.recorderManager?.onResume(() => {
          console.log('recorder onResume');
        })
        if (this.recording) {
          this.disableStartBtn = false
          this.disablePauseBtn = false
          this.disableResumeBtn = true
          this.recordTimeInterval = setInterval(() => {
            this.recordTime += 1;
            this.formatedRecordTime = this.formatTime(this.recordTime);
          }, 1000)
        }
      },
      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(":");

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

        console.log('startRecord', this.items[this.current].value)
        // TODO ios 在没有请求过权限之前无法得知是否有相关权限,这种状态下需要直接调用录音,但没有状态或回调判断用户拒绝
        this.recorderManager?.start({
          format: this.items[this.current].value,
          sampleRate: 8000,
          numberOfChannels: 2,
          encodeBitRate: 48000,
          frameSize: 2
        });
      },
      stopRecord() { //停止录音
        this.recorderManager?.stop();
        this.disableStartBtn = false
        this.disablePauseBtn = false
        this.disableResumeBtn = false
      },
      playVoice() {
        if (this.recording) {
          uni.showToast({
            title: "当前录音还未结束"
          })
          return
        }
        console.log('play voice');
        if (this.playing) {
          return
        }
        this.playing = true;
        this.playTimeInterval = setInterval(() => {
          if (this.playTime < this.recordTime) {
            this.playTime += 1;
          }
          this.formatedRecordTime = this.formatTime(this.playTime);
        }, 1000)
        this.music?.play();
      },
      stopVoice() {
        if (this.recording) {
          uni.showToast({
            title: "当前录音还未结束"
          })
          return
        }
        clearInterval(this.playTimeInterval)
        this.playing = false;
        this.formatedRecordTime = this.formatTime(0);
        this.playTime = 0;
        this.music?.stop();
      },
      end() {
        this.music?.stop();
        this.recorderManager?.stop();
        clearInterval(this.recordTimeInterval)
        clearInterval(this.playTimeInterval);
        this.recording = false
        this.playing = false
        this.hasRecord = false;
        this.playTime = 0
        this.recordTime = 0;
        this.formatedRecordTime = "00:00:00"
        this.formatedRecordTime = "00:00:00";
      },
      clear() {
        this.end();
      }
    }
  }
</script>

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

  .time-big {
    font-size: 60rpx;
    margin: 20rpx;
  }

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

# 参见

# 通用类型

# GeneralCallbackResult

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