在微信小程序开发中实现录音

核心api

使用微信小程序提供的 wx.getRecorderManager() API来实现录音功能,这是微信官方推荐的录音解决方案。

初始化recorderManager实现

1
2
3
4
5
// 初始化录音管理器
const recorderManager = wx.getRecorderManager();
this.setData({
recorderManager: recorderManager
});

recorder配置参数

1
2
3
4
5
6
7
8
const options = {
duration: 600000, // 最大录音时长(10分钟)
sampleRate: 16000, // 采样率
numberOfChannels: 1, // 声道数
encodeBitRate: 96000, // 编码码率
format: 'mp3', // 录音格式
frameSize: 50 // 帧大小
};

开始录音

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
startRecord: function () {
this.setData({
recordState: 1 # 更改页面录音状态(用于控制UI)
});

// 开始录音
const options = {
duration: this.data.maxDuration * 1000, // 最大录音时长(毫秒)
sampleRate: 16000,
numberOfChannels: 1,
encodeBitRate: 96000,
format: 'mp3',
frameSize: 50
};
this.data.recorderManager.start(options); # 启动录音
},

结束录音

1
2
3
4
stopRecord: function () {
// 停止录音
this.data.recorderManager.stop();
},

录音停止时回调(保存录音文件)

1
2
3
4
5
6
recorderManager.onStop((res) => {
this.setData({
tempFilePath: res.tempFilePath,
hasRecord: true
});
});

录音上传到云

核心API

使用微信云开发提供的 wx.cloud.uploadFile() API将录音文件上传到云存储,实现录音文件的云端持久化存储。

1
2
3
4
5
// 云开发初始化(通常在app.js中配置)
wx.cloud.init({
env: 'your-env-id', // 替换为你的云开发环境ID
traceUser: true
});

上传前准备

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 检查是否有录音文件
if (!this.data.tempFilePath) {
wx.showToast({
title: '没有录音文件',
icon: 'none'
});
return;
}

// 设置上传状态
this.setData({
uploading: true,
uploadResult: ''
});

执行文件上传

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
uploadRecord: function () {
// 检查录音文件是否存在
if (!this.data.tempFilePath) {
wx.showToast({ title: '没有录音文件', icon: 'none' });
return;
}

// 设置上传状态
this.setData({ uploading: true, uploadResult: '' });

// 执行上传
wx.cloud.uploadFile({
cloudPath: `voice/${Date.now()}.mp3`, // 云存储路径:使用时间戳命名避免冲突
filePath: this.data.tempFilePath, // 本地临时文件路径
success: (res) => {
console.log('文件上传成功,fileID:', res.fileID);
this.setData({
uploadResult: '上传成功',
uploading: false
});
wx.showToast({ title: '上传成功', icon: 'success' });
},
fail: (err) => {
console.error('文件上传失败', err);
this.setData({
uploadResult: '上传失败:' + err.errMsg,
uploading: false
});
wx.showToast({ title: '上传失败', icon: 'none' });
}
});
}

播放录音

核心API

使用微信小程序提供的 wx.createInnerAudioContext() API创建音频播放实例,实现录音文件的播放控制。

初始化音频播放管理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// 初始化音频播放管理器
initInnerAudioContext: function () {
  const innerAudioContext = wx.
  createInnerAudioContext();
  this.setData({
    innerAudioContext: 
    innerAudioContext
  });

  // 播放结束回调
  innerAudioContext.onEnded(() => {
    this.setData({ playing: 
    false });
  });

  // 播放错误回调
  innerAudioContext.onError((err) 
  => {
    console.error('播放错误', err);
    this.setData({ playing: 
    false });
    wx.showToast({ title: '播放失败
    ', icon: 'none' });
  });
}

音频播放配置(可选)

1
2
3
4
5
6
7
8
9
// 可在初始化时配置音频播放参数
const innerAudioContext = wx.
createInnerAudioContext();
innerAudioContext.volume = 1; // 音
量(0-1)
innerAudioContext.autoplay = 
false; // 是否自动播放
innerAudioContext.loop = false; // 
是否循环播放

播放/暂停控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
playRecord: function () {
  if (this.data.playing) {
    // 正在播放时,停止播放
    this.data.innerAudioContext.stop
    ();
    this.setData({ playing: 
    false });
  } else {
    // 未播放时,开始播放
    this.data.innerAudioContext.src 
    = this.data.tempFilePath; // 设
    置音频源
    this.data.innerAudioContext.play
    (); // 启动播放
    this.setData({ playing: true });
  }
}

播放状态管理

1
2
3
4
5
6
7
data: {
  playing: false, // 是否正在播放
  tempFilePath: '', // 录音临时文件路
  径
  hasRecord: false // 是否有可用录音文
  件
}

生命周期管理

1
2
3
4
5
6
7
8
// 页面卸载时清理资源
onUnload: function () {
  // 停止播放
  if (this.data.innerAudioContext) {
    this.data.innerAudioContext.stop
    ();
  }
}

完整功能流程

1. 录音流程

开始录音 → 录音中(计时) → 结束录音 → 获取临时文件路径 → 保存到页面数据

2. 播放流程

检查是否有录音文件 → 点击播放按钮 → 设置音频源 → 调用play() → 播放中 → 播放结束/点击暂停 → 停止播放

3. 上传流程

检查是否有录音文件 → 点击上传按钮 → 设置上传状态 → 调用cloud.uploadFile() → 上传成功/失败 → 更新上传结果 → 显示提示

注意事项

  1. 确保已在微信开发者工具中初始化云开发环境
完整后端代码示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
Page({
data: {
content: '', // 存储文本内容
recordState: 0, // 0: 未录音, 1: 录音中
recorderManager: null, // 录音管理器
innerAudioContext: null, // 音频播放管理器
recordTimer: null, // 录音计时器
recordDuration: 0, // 录音时长(秒)
maxDuration: 600, // 最大录音时长(10分钟=600秒)
tempFilePath: '', // 录音临时文件路径
hasRecord: false, // 是否有录音文件
playing: false, // 是否正在播放
uploading: false, // 是否正在上传
uploadResult: '' // 上传结果
},

onLoad: function () {
// 初始化录音管理器和音频播放管理器
this.initRecorderManager();
this.initInnerAudioContext();
},

// 初始化录音管理器
initRecorderManager: function () {
const recorderManager = wx.getRecorderManager();
this.setData({
recorderManager: recorderManager
});

// 录音开始回调
recorderManager.onStart(() => {
console.log('录音开始');
// 开始计时
this.startRecordTimer();
});

// 录音停止回调
recorderManager.onStop((res) => {
console.log('录音停止', res);
// 停止计时
this.stopRecordTimer();
// 重置录音时长
this.setData({
recordDuration: 0,
recordState: 0,
tempFilePath: res.tempFilePath,
hasRecord: true
});
});

// 录音错误回调
recorderManager.onError((err) => {
console.error('录音错误', err);
// 停止计时
this.stopRecordTimer();
// 重置录音时长和状态
this.setData({
recordDuration: 0,
recordState: 0
});
wx.showToast({
title: '录音失败',
icon: 'none'
});
});
},

// 初始化音频播放管理器
initInnerAudioContext: function () {
const innerAudioContext = wx.createInnerAudioContext();
this.setData({
innerAudioContext: innerAudioContext
});

// 播放结束回调
innerAudioContext.onEnded(() => {
console.log('播放结束');
this.setData({
playing: false
});
});

// 播放错误回调
innerAudioContext.onError((err) => {
console.error('播放错误', err);
this.setData({
playing: false
});
wx.showToast({
title: '播放失败',
icon: 'none'
});
});
},

// 开始录音计时
startRecordTimer: function () {
this.setData({
recordTimer: setInterval(() => {
this.setData({
recordDuration: this.data.recordDuration + 1
});
// 检查是否达到最大时长
if (this.data.recordDuration >= this.data.maxDuration) {
// 自动停止录音
this.data.recorderManager.stop();
}
}, 1000)
});
},

// 暂停录音计时
pauseRecordTimer: function () {
if (this.data.recordTimer) {
clearInterval(this.data.recordTimer);
this.setData({
recordTimer: null
});
}
},

// 停止录音计时
stopRecordTimer: function () {
this.pauseRecordTimer();
},

// 开始录音
startRecord: function () {
// 设置录音状态为录音中
this.setData({
recordState: 1
});

// 开始录音
const options = {
duration: this.data.maxDuration * 1000, // 最大录音时长(毫秒)
sampleRate: 16000,
numberOfChannels: 1,
encodeBitRate: 96000,
format: 'mp3',
frameSize: 50
};
this.data.recorderManager.start(options);
},

// 结束录音
stopRecord: function () {
// 停止录音
this.data.recorderManager.stop();
},

// 播放录音
playRecord: function () {
if (this.data.playing) {
// 如果正在播放,停止播放
this.data.innerAudioContext.stop();
this.setData({
playing: false
});
} else {
// 如果没有播放,开始播放
this.data.innerAudioContext.src = this.data.tempFilePath;
this.data.innerAudioContext.play();
this.setData({
playing: true
});
}
},

// 上传录音到云服务器
uploadRecord: function () {
if (!this.data.tempFilePath) {
wx.showToast({
title: '没有录音文件',
icon: 'none'
});
return;
}

// 设置上传状态
this.setData({
uploading: true,
uploadResult: ''
});

// 上传录音文件到云存储
wx.cloud.uploadFile({
cloudPath: `voice/${Date.now()}.mp3`, // 云存储路径
filePath: this.data.tempFilePath, // 本地临时文件路径
success: (res) => {
console.log('文件上传成功,fileID:', res.fileID);
this.setData({
uploadResult: '上传成功',
uploading: false
});
wx.showToast({
title: '上传成功',
icon: 'success'
});
},
fail: (err) => {
console.error('文件上传失败', err);
this.setData({
uploadResult: '上传失败:' + err.errMsg,
uploading: false
});
wx.showToast({
title: '上传失败',
icon: 'none'
});
}
});
},

// 保存内容到本地存储
saveContent: function () {
wx.setStorageSync('fastTestContent', this.data.content);
},

// 生命周期函数--监听页面显示
onShow: function () {
// 从本地存储获取内容
const content = wx.getStorageSync('fastTestContent') || '';
this.setData({
content: content
});
},

// 生命周期函数--监听页面卸载
onUnload: function () {
// 停止录音计时
this.stopRecordTimer();
// 停止录音
if (this.data.recorderManager) {
this.data.recorderManager.stop();
}
// 停止播放
if (this.data.innerAudioContext) {
this.data.innerAudioContext.stop();
}
}
});
前端测试代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<view class="yuyinWrap">
<!-- 文本输入框:可以手动输入 -->


<!-- 录音控制按钮 -->
<view class="btnGroup">
<button
class="yuyinBtn {{recordState==1 ? 'yuyinBtnBg':''}}"
bindtap="startRecord"
disabled="{{recordState==1}}">
<text>开始录音</text>
</button>
<button
class="yuyinBtn {{recordState==1 ? 'stopBtn':''}}"
bindtap="stopRecord"
disabled="{{recordState!=1}}">
<text>结束录音</text>
</button>
</view>

<!-- 录音时长显示 -->
<view class="duration" wx:if="{{recordState == 1}}">
录音时长:{{recordDuration}}秒
</view>

<!-- 播放和上传按钮 -->
<view class="btnGroup" wx:if="{{hasRecord}}">
<button
class="yuyinBtn {{playing ? 'playingBtn':''}}"
bindtap="playRecord"
disabled="{{uploading}}">
<text>{{playing ? '停止播放' : '播放录音'}}</text>
</button>
<button
class="yuyinBtn {{uploading ? 'uploadingBtn':''}}"
bindtap="uploadRecord"
disabled="{{uploading || playing}}">
<text>{{uploading ? '上传中...' : '上传到云服务器'}}</text>
</button>
</view>

<!-- 状态信息 -->
<view class="statusInfo" wx:if="{{uploadResult}}">
<text>上传结果:{{uploadResult}}</text>
</view>
</view>

使用微信同声传译拼接实现长时间转译

前端测试代码

前端实验代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<view class="yuyinWrap">
<!-- 文本输入框:可以手动输入,也可以语音识别输入 -->
<textarea
id="yuyinTextarea"
class='yuyinCon' placeholder='请输入内容' maxlength="-1"
value='{{recordState == 1 ? baseContent + tempContent : content}}'
></textarea>
<!-- 语音按钮 点击开始/停止录音 -->
<view>
<button
class="yuyinBtn {{recordState==1 || recordState==2 ? 'yuyinBtnBg':''}}"
bindtap="toggleRecord"
disabled="{{recordState==2}}"
>
<text wx:if="{{recordState == 0 || recordState == 3}}">开始录音</text>
<text wx:if="{{recordState == 1}}">停止录音</text>
<text wx:if="{{recordState == 2}}">语音识别中...</text>
</button>
</view>
</view>

后端代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
// 引入插件
const plugin = requirePlugin('WechatSI');
// 获取全局唯一语音识别管理器
const manager = plugin.getRecordRecognitionManager();
Page({
data: {
// 录音状态0未在录音1正在录音2语音识别中3语音识别结束
recordState: 0,
content: '', // 最终内容
tempContent: '', // 实时识别的临时内容
baseContent: '', // 开始录音时的基础内容
isAutoRestart: false, // 是否自动重启录音
recordTimer: null, // 录音时长定时器
},
onLoad() {
this.initSI();
},

onReady() {
// 页面渲染完成后滚动到底部
this.scrollToBottom();
},
// 插件初始化
initSI() {
const that = this;
// 有新的识别内容返回,则会调用此事件
manager.onRecognize = function (res) {
console.log(res);
// 实时更新识别内容
that.setData({
tempContent: res.result,
});
// 移除实时识别时的自动滚动,只在关键节点滚动
};
// 正常开始录音识别时会调用此事件
manager.onStart = function (res) {
console.log('成功开始录音识别', res);
// 开始录音时-抖动一下手机
wx.vibrateShort({ type: 'medium' });

// 设置55秒后自动停止录音(预留5秒处理时间)
clearTimeout(that.data.recordTimer);
that.setData({
recordTimer: setTimeout(() => {
if (that.data.recordState == 1) {
console.log('自动停止录音,即将重启');
// 设置自动重启标志
that.setData({ isAutoRestart: true });
manager.stop();
}
}, 55000)
});
};
// 识别错误事件
manager.onError = function (res) {
console.error('error msg', res);
const tips = {
'-30003': '说话时间间隔太短,无法识别语音',
'-30004': '没有听清,请再说一次~',
'-30011': '上个录音正在识别中,请稍后尝试',
};
const retcode = res?.retcode.toString();
retcode &&
wx.showToast({
title: tips[`${retcode}`],
icon: 'none',
duration: 2000,
});
};
// 识别结束事件
manager.onStop = function (res) {
console.log('..............结束录音', res);
console.log('录音临时文件地址 -->', res.tempFilePath);
console.log('录音总时长 -->', res.duration, 'ms');
console.log('文件大小 --> ', res.fileSize, 'B');
console.log('语音内容 --> ', res.result);

// 清空定时器
clearTimeout(that.data.recordTimer);

// 处理识别结果
let recognizedText = res.result;
if (recognizedText === '') {
console.log('本次识别结果为空');
recognizedText = '';
}

// 合并最终结果
var finalText = that.data.baseContent + recognizedText;
that.setData({
content: finalText,
tempContent: '', // 清空临时内容
});

// 检查是否需要自动重启
if (that.data.isAutoRestart && that.data.recordState == 1) {
console.log('自动重启录音');
// 短暂延迟后重启,避免接口调用过于频繁
setTimeout(() => {
// 更新baseContent为当前最新内容
that.setData({
baseContent: finalText,
});
// 重新开始录音
manager.start({
duration: 30000,
lang: 'zh_CN',
});
}, 300);
} else {
// 手动停止录音,更新状态
that.setData({
recordState: 3,
});
// 录音结束后滚动到底部
that.scrollToBottom();
}
};
},
// 点击按钮-切换录音状态
toggleRecord() {
if (this.data.recordState == 0 || this.data.recordState == 3) {
// 开始录音
this.startRecording();
} else if (this.data.recordState == 1) {
// 手动停止录音
this.stopRecording();
}
},

// 开始录音
startRecording() {
// 保存当前内容作为基础内容
this.setData({
recordState: 1,
baseContent: this.data.content,
tempContent: '',
isAutoRestart: true, // 开启自动重启
});
// 语音识别开始
manager.start({
duration: 30000,
lang: 'zh_CN',
});
},

// 停止录音
stopRecording() {
// 关闭自动重启
this.setData({
isAutoRestart: false,
});
// 语音识别结束
manager.stop();
},

// 自动滚动到底部
scrollToBottom() {
// 使用setTimeout确保DOM已更新
setTimeout(() => {
const query = wx.createSelectorQuery();
query.select('#yuyinTextarea').fields({
scrollHeight: true,
size: true
}, (res) => {
if (res) {
// 获取textarea组件实例
const textareaContext = wx.createSelectorQuery();
textareaContext.select('#yuyinTextarea').context((ctxRes) => {
if (ctxRes && ctxRes.context) {
// 设置滚动位置到最底部
ctxRes.context.scrollTop(res.scrollHeight);
}
}).exec();
}
}).exec();
}, 50);
},

// 页面卸载时清理资源
onUnload() {
// 清空定时器
clearTimeout(this.data.recordTimer);
// 确保录音已停止
if (this.data.recordState == 1) {
manager.stop();
}
},
});