最近在开发微信小程序,遇到一个需求,实现类似于微信发送语音的效果,但是我这个是发送语音后直接转为文字展示。
- 先说微信小程序的textarea
- 0/200. 是限制用户输入的最长长度
- 0这个位置 首先是根据用户输入的长度变化而变化的
- 200 是用户输入最长度
- 通过bindinput去监听用户的输入的长度,如果用户输入小于最大值,将长度赋值给0的位置
- 在用真机调试的时候发现,textarea的输入值还有placeholder回悬浮在手机上,通过多次查询发现给textarea添加fixed属性会解决
- 在真机调试的时候发现一点开textarea这个页面,输入框就会自动谈到顶部,通过 cursor-spacing=“90” 会解决这个问题
<textarea fixed="{{true}}" class='yuyinCon' maxlength="{{orderNoteMax}}" placeholder='请输入内容'
value='{{noteNum}}' cursor-spacing="90" bindinput="bindInputNote" placeholder-class="init_textarea">
<text class="currentWordNumber">{{currentWordNumber|0}}/{{orderNoteMax}}</text>
</textarea>
bindInputNote(e){
this.setData({
noteNum:e.detail.value.trim()
})
var len = ((e.detail.value).toString()).length
if(len > this.data.orderNoteMax) return;
this.setData({
currentWordNumber:len
})
}
再来说实现录制语音—通过微信小程序的同声传译的插件
- 需要在微信公众平台去添加插件
- 在app.json添加
"plugins": {
"WechatSI": {
"version": "插件的最新版本号",
"provider": "插件的AppID"
}
},
- 在需要的页面中先
//引入插件:微信同声传译
const plugin = requirePlugin('WechatSI');
//获取全局唯一的语音识别管理器recordRecoManager
const manager = plugin.getRecordRecognitionManager();
- 对同声传译进行初始化
//识别语音 -- 初始化
initRecord(){
const that = this;
// 当有新的识别内容返回,则会调用此事件
manager.onRecognize = function(res){
console.log(res)
}
// 正常开始录音识别时会调用此事件
manager.onStart = function(res){
console.log("成功开始录音识别", res)
}
// 识别错误事件
manager.onError = function (res) {
console.error("error msg", res)
}
//识别结束事件
manager.onStop = function(res){
console.log('..............结束录音')
console.log('录音临时文件地址 -->' + res.tempFilePath);
console.log('录音总时长 -->' + res.duration + 'ms');
console.log('文件大小 --> ' + res.fileSize + 'B');
console.log('语音内容 --> ' + res.result);
if (res.result == '') {
wx.showModal({
title: '提示',
content: '听不清楚,请重新说一遍!',
showCancel: false,
success: function (res) { }
})
return;
}
}
},
可以看到录音的整个流程–实现过程
- 给按住说话的button绑定catchtouchstart事件,首先根据wx.getSetting判断用户是否打开了录音权限,如果没有打开,则通过wx.authorize,向用户打开授权请求,如果用户拒绝了,就给用户打开授权设置页面。
- 如果用户一开始就授权了录音设置,则调用语音开始录制的事件
- 给button绑定catchtouchend事件,触发语音录制结束事件
- 在语音录制结束后,还可以拿到录音的总时长,具体怎么展示可以自定义。我们可以将录音文件传给后端,后端返回可以播放的录音,在前端就可以播放这段录音。
1. 通过创建 wx.createInnerAudioContext();`
tips:
在代码过程中,我通过catchtouchstart和catchtouchend分出触发点击录制和松开发送两个过程,我本想通过两个事件分别弹出“按住说话”和“松开发送”,经查验发现catchtouchend一直没有触发,查了好久才发现
1. “按住说话”和“松开发送" 我是通过wx:if去判断的,分别在两个事件里面去设置true or false
2. 按照流程,首先触发catchtouchstart,然后通过setData()去修改参数
3. 发现setData()会刷新页面,所以catchtouchend一直触发不了,
4. 我现在是吧两个弹框柔道一起,其他有待研究。。。
具体代码
<view class="yuyinWrap">
<textarea class='yuyinCon' fixed maxlength="{{orderNoteMax}}" placeholder='请输入内容'
value='{{noteNum}}' cursor-spacing="90" bindinput="bindInputNote" placeholder-class="init_textarea">
<text class="currentWordNumber">{{currentWordNumber|0}}/{{orderNoteMax}}</text>
</textarea>
<!-- -->
<view class=''>
<button class="yuyinBtn {{recordState == true ? 'yuyinBtnBg':''}}" catchtouchstart="touchStart"
catchtouchend="touchEnd">
按住 说话
</button>
</view>
<view class="output-audio" wx:if="{{yuyinTime > 0}}">
<!-- 默认状态isplay=F -->
<view wx:if="{{isPlay === false}}" bindtap="bindPlay" style='width:160rpx' class="audio">
<view class="yuyin_icon">
<image style="width:100%;height:100%" src="../../images/vedio@2x.png"></image>
</view>
<view class="yuyin_time">{{yuyinTime}}s</view>
</view>
<!-- 正在播放状态isplay=T -->
<view wx:if="{{isPlay === true}}" bindtap="bindStop" style='width:160rpx' class="audio">
<view class="yuyin_icon_act">
<image wx:if="{{isImg === 1}}" style="width:100%;height:100%" src="../../images/vedio3@2x.png">
</image>
<image wx:elif="{{isImg === 2}}" style="width:100%;height:100%" src="../../images/vedio2@2x.png">
</image>
<image wx:elif="{{isImg === 3}}" style="width:100%;height:100%" src="../../images/vedio1@2x.png">
</image>
</view>
<view class="yuyin_time">{{yuyinTime}}s</view>
</view>
</view>
<mp-loading wx:if="{{isLoading}}" type="circle"></mp-loading>
<!-- 开始语音 弹出语音图标表示正在录音 -->
<cover-view class="startYuyinImage" wx:if="{{recordState == true}}">
<cover-image src="../../images/yuyin.png"></cover-image>
<cover-view>开始语音,松开发送</cover-view>
</cover-view>
</view>
//识别语音 -- 初始化
initRecord: function () {
const that = this;
// 有新的识别内容返回,则会调用此事件
manager.onRecognize = function (res) {
console.log("读取到新内容:", res)
}
// 正常开始录音识别时会调用此事件
manager.onStart = function (res) {
console.log("成功开始录音识别", res)
that.setData({
isLoading: false,
recordState: true, //录音状态
})
}
// 识别错误事件
manager.onError = function (res) {
console.error("error msg", res)
}
//识别结束事件
manager.onStop = function (res) {
console.log('..............结束录音')
console.log('录音临时文件地址 -->' + res.tempFilePath);
console.log('录音总时长 -->' + res.duration + 'ms');
console.log('文件大小 --> ' + res.fileSize + 'B');
console.log('语音内容 --> ' + res.result);
if (res.result == '') {
wx.showModal({
title: '提示',
content: '听不清楚,请重新说一遍!',
showCancel: false,
success: function (res) { }
})
return;
}
var tempImagePath = res.tempFilePath
wx.uploadFile({
url: `${app.globalData.baseUrl}/upload/audio`, //此处换上你的接口地址
filePath: tempImagePath,
name: 'file',
formData: {
duration: parseInt(parseInt(res.duration) / 1000)
},
header: {
// "Content-Type": "multipart/form-data",
// 'accept': 'application/json',
'Authorization': app.getToken() //若有token,此处换上你的token,没有的话省略
},
success: function (resaudio) {
if (resaudio.statusCode == 200) {
if (JSON.parse(resaudio.data).code == 200) {
var text = res.result;
// 获取输入框内容的长度
var len = parseInt(text.length);
//最多字数限制
if (len > that.data.orderNoteMax) return;
// 当输入框内容的长度大于最大长度限制(max)时,终止setData()的执行
that.setData({
currentWordNumber: len //当前字数
});
that.setData({
noteNum: text,
recordState: false,
video: JSON.parse(resaudio.data).data.path,
yuyinTime: parseInt(parseInt(res.duration) / 1000),
audioId: JSON.parse(resaudio.data).data.id
}, () => {
that.checkLogin()
})
} else if (JSON.parse(resaudio.data).code == 401) {
wx.showModal({
title: '登录',
showCancel:false,
content: '确定重新登录?',
confirmColor: "#000000",
success: function (res) {
if (res.cancel) {
//点击取消,默认隐藏弹框
} else {
//点击确定
wx.reLaunch({
url: '../login/index',
})
return false;
}
},
fail: function (res) { },//接口调用失败的回调函数
complete: function (res) { },//接口调用结束的回调函数(调用成功、失败都会执行)
})
} else {
this.setData({
error: JSON.parse(resaudio.data).message || 'Error',
})
}
}
},
fail: function (res) {
console.log(res);
},
})
}
},
getSeeting() {
const _this = this
wx.getSetting({//获取用户当前设置
success: res => {
if (res.authSetting['scope.record']) {//查看是否授权了录音设置
// 用户已经授权
console.log("开始授权")
_this.setData({
init: false,
isLoading: true
})
_this.timer = setTimeout(() => {
manager.start({
duration: 30000,
lang: 'zh_CN',// 识别的语言,目前支持zh_CN en_US zh_HK sichuanhua
})
}, 10)
} else {
// 用户还没有授权,向 用户发起授权请求
wx.authorize({//提前向用户发起授权请求,调用后会立刻弹窗询问用户是否同意授权小程序使用某项功能或获取用户的某些数据,但不会实际调用对应接口
scope: 'scope.record',
success() {//用户同意授权摄像头
console.log("同意授权")
},
fail() {//用户不同意授权摄像头
_this.openSetting()
}
})
}
},
fail() {
console.log('获取用户授权信息失败')
}
})
},
//语音 --按住说话
touchStart: function (e) {
this.getSeeting()
},
//语音 --松开结束
touchEnd: function (e) {
console.log("结束录音")
this.setData({
recordState: false,
init: true,
isLoading: false
})
manager.stop();
clearTimeout(this.timer)
console.log(this.data.recordState)
},
bindPlay() {
console.log('播放监听')
this.setData({
isPlay: true,
})
myaudio.src = this.data.video
myaudio.autoplay = true
setTimeout(() => {
myaudio.play()
}, 500)
myaudio.onPlay(() => {
var num = 1
this.imgTimer = setInterval(() => {
if (num > 3) {
num = 1
} else {
num++
}
this.setData({
isImg: num
})
this.setData({
})
}, 500)
})
myaudio.onEnded(() => {
this.setData({
isPlay: false,
isImg: 1,
imgTimer: null,
})
console.log(this.data.yuyinTime)
clearInterval(this.data.imgTimer)
})
},
bindStop() {
this.setData({
isPlay: false,
isImg: 1,
imgTimer: null,
})
myaudio.stop()
clearInterval(this.data.imgTimer)
},