We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
最近上线了一款智能外呼机器人产品,需要开发一款录音、上传、播放音频功能的 微信小程序给录音师配置外呼话术真人录音。
代码已开源,数据均已本地化处理。适合新手参考学习的完整原生微信小程序小项目。
磨刀不误砍柴工,良好的约束是一个团队协作开发质量保障的基础。
最新的小程序已支持,less、ts 文件,无需在配置转换插件。
project.config.json 中配置开启支持编译
project.config.json
{ "setting": { "useCompilerPlugins": [ "typescript", "less" ] } }
typings/
.d.ts
namespace
import、export
/// <reference path="./wx/index.d.ts" />
interface
I
type
T
N
注意:提交审核时,审核人员在测试你的应用时,相当于 develop 环境。 如果公司做了测试环境内网隔离,需要小心,审核人员无法正常访问。 本地开发完时需要手动改成线上环境,比较繁琐。
develop
const { miniProgram: { envVersion } } = wx.getAccountInfoSync(); const getConfig = () => { switch (envVersion) { case 'develop':// 开发版 return { env: envVersion, host: prodHost, } case 'trial':// 体验版 return { env: envVersion, host: testHost, } case 'release': // 正式版 return { env: envVersion, host: prodHost, } } } export const envConfig = getConfig();
小程序不支持 cookie,用户登陆状态需要自己存储。
cookie
思路:存储在 Storage 中,请求时在 header 中设置 cookie 字段, 处理响应时存储返回的 cookie。
Storage
header
注意:开发、体验、线上小程序使用的同一个 Storage,存储时记得做隔离。例如:
const { miniProgram: { envVersion } } = wx.getAccountInfoSync(); export const StorageMap = { Cookie: `${envVersion}_cookie`, };
主要使用以下 api
PS.
audio
InnerAudioContext
process
BackgroundAudioManager
为什么要声明全局变量:
const recorderManager: WechatMiniprogram.RecorderManager = wx.getRecorderManager(); const innerAudioContext: WechatMiniprogram.InnerAudioContext = wx.createInnerAudioContext();
const recordOptions = { duration: 10 * 60 * 1000, // 最多录音时长 10 分钟 sampleRate: 8000, // 采样率 numberOfChannels: 1, // 1 个录音通道即可 format: 'wav', // 服务端指定格式 }; recorderManager.start(recordOptions)
RecorderManager.onFrameRecorded
wav
// 监听已录制完指定帧大小的文件事件。如果设置了 frameSize,则会回调此事件。 recorderManager.onFrameRecorded(({ frameBuffer, isLastFrame }) => { console.log('frameBuffer.byteLength: ', frameBuffer.byteLength) console.log('isLastFrame: ', isLastFrame); });
recorderManager.onStart(() => { console.log('recorder start'); this.startClock(); this.setData({ ...recordingData, }); });
CDN
recorderManager.stop();
// 停止录音事件 recorderManager.onStop(async (res) => { console.log('recorder stop', res) // 停止后立即更新状态,以免异常 this.stopClock(); this.setData({ ...initRData, }); if (isError) { isError = false; return; } const { tempFilePath, duration } = res; console.log('tempFilePath', tempFilePath); const url = await uploadFile({ filePath: tempFilePath }); // 快速开始时,获取的都是未录音,会冲掉当前上传试听,这里手动设置一下 if (innerAudioContext.currentTime) { innerAudioContext.stop(); } innerAudioContext.src = url; this.setData({ ...initPlayData, ...initRData, detail: { ...this.data.detail, url, duration: Math.ceil(duration / 1000), }, duration: formatClock(duration, true), }); // await this.getDetail('CUR'); });
true
onStop
// 监听录音错误事件 recorderManager.onError((err) => { this.noEffectStopRecorder(); showErrMsg(msgMap[err.errMsg] || err.errMsg || '小程序错误'); console.log('recorderManager.onError', err); }); // 监听录音暂停事件 recorderManager.onPause(() => { console.log('recorder pause'); // 立马停止,重新开始,没有恢复机制 this.noEffectStopRecorder(); });
stop
noEffectStopRecorder() { if (this.data.isRecording) { isError = true; recorderManager.stop(); } }
export function uploadFile({ fileName, filePath }: { fileName?: string; filePath: string; }) { return new Promise<string>((resolve) => { wx.showLoading({ title: '上传中...', }); const name = fileName || filePath; // 获取 CDN token getNosToken({ fileName: name }).then((data) => { console.log('uploadToken: ', data); wx.hideLoading(); wx.uploadFile({ url: 'https://nos.com/', name: 'file', // 服务器获取流的参数名 filePath, formData: { Object: data.objectName, 'x-nos-token': data.token, }, success(res) { console.log('上传成功回调', res); wx.hideLoading(); const url = `https://cdn.com/${data.objectName}` console.log(url); resolve(url); }, fail(err) { wx.hideLoading(); wx.showToast({ title: err.errMsg, icon: 'none', }); reject(err); }, }) }); }); }
// 监听音频播放事件 innerAudioContext.onPlay(() => { console.log('开始播放'); this.setData({ ...playingData, }); });
// 监听音频播放进度更新事件 innerAudioContext.onTimeUpdate(() => { console.log('监听音频播放进度更新事件'); let playPercent = 0; const duration = this.data.detail.duration || innerAudioContext.duration; try { playPercent = Math.ceil(((innerAudioContext.currentTime * 1000) / (duration * 1000)) * 100) || 0; } catch (e) { playPercent = 0; } playPercent = playPercent && playPercent > 100 ? 100 : playPercent; const currentTime = formatClock(innerAudioContext.currentTime * 1000, true); console.log('当前播放时间:', currentTime); console.log('微信暴露时间:', innerAudioContext.duration); console.log('后端返回时间:', duration); console.log('当前播放进度:', playPercent); this.setData({ currentTime, playPercent, }); });
// 监听音频自然播放至结束的事件 innerAudioContext.onEnded(() => { console.log('监听音频自然播放至结束的事件'); this.setData({ ...initPlayData }); }); // 监听音频播放错误事件 innerAudioContext.onError((res) => { /** * 10001 系统错误 * 10002 网络错误 * 10003 文件错误 * 10004 格式错误 * -1 未知错误 */ console.log(res.errCode, res.errMsg); this.setData({ ...initPlayData }); }); // 监听音频暂停事件 innerAudioContext.onPause(() => { console.log('监听音频暂停事件'); this.setData({ ...initPlayData }); }); // 监听音频停止事件 innerAudioContext.onStop(() => { console.log('监听音频停止事件'); this.setData({ ...initPlayData, }); });
/** * 生命周期函数--监听页面初次渲染完成 */ onReady() { this.initRecorder(); this.initAudioPlayer(); }, /** * 生命周期函数--监听页面显示 */ onShow() { this.getDetail('CUR'); }, /** * 生命周期函数--监听页面卸载 */ onUnload() { console.log('切换页面停止录音或播放'); innerAudioContext.stop(); this.noEffectStopRecorder(); },
The text was updated successfully, but these errors were encountered:
No branches or pull requests
录音、上传、播放音频微信小程序实践
最近上线了一款智能外呼机器人产品,需要开发一款录音、上传、播放音频功能的
微信小程序给录音师配置外呼话术真人录音。
代码已开源,数据均已本地化处理。适合新手参考学习的完整原生微信小程序小项目。
约束
磨刀不误砍柴工,良好的约束是一个团队协作开发质量保障的基础。
开启 ts、less
最新的小程序已支持,less、ts 文件,无需在配置转换插件。
project.config.json
中配置开启支持编译ts 声明
typings/
文件夹中,.d.ts
命名namespace
来隔离,文件中不能使用import、export
,业务中直接使用声明即可,很方便.d.ts
之间引用,使用/// <reference path="./wx/index.d.ts" />
interface
变量可以I
开头,同理type
T
开头,namespace
N
开头环境隔离
注意:提交审核时,审核人员在测试你的应用时,相当于
develop
环境。如果公司做了测试环境内网隔离,需要小心,审核人员无法正常访问。
本地开发完时需要手动改成线上环境,比较繁琐。
模拟 cookie
小程序不支持
cookie
,用户登陆状态需要自己存储。思路:存储在
Storage
中,请求时在header
中设置cookie
字段,处理响应时存储返回的
cookie
。注意:开发、体验、线上小程序使用的同一个
Storage
,存储时记得做隔离。例如:实践分析
依赖接口
主要使用以下 api
PS.
audio
组件样式不符合需求,目前只需播放进度条,InnerAudioContext
配合process
组件实现InnerAudioContext
退出小程序自动停止播放,需要退出小程序依然可播放请使用背景音频BackgroundAudioManager
代替为什么要声明全局变量:
录音
RecorderManager.onFrameRecorded
来感知是否收到声音,展示波形图,但该事件不支持
wav
格式文件。目前监听到开始事件即显示录音计时。CDN
,然后把地址存储到业务服务器,接着试听播放。比如电话会打断录音,触发暂停事件。拒绝授权会出发错误事件。这里都设置异常变量为
true
,在onStop
事件中不进行上传逻辑,而是恢复到录音初始状态。stop
是无副作用的。未开始或暂停录音调用stop
会抛出异常。小心导致死循环。上传
播放
process
百分比Page 事件
参考
The text was updated successfully, but these errors were encountered: