官方文档必须首当其冲
判断是否是微信环境 不是的话跳到 微信提示页面
var ua = navigator.userAgent.toLowerCase();
var isWeixin = ua.indexOf('micromessenger') != -1;
if (!isWeixin) { window.location.href = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=888" }
首先H5录音功能的话 对于普通H5网上是有很多的方法 插件 但是兼容性很差 特别是对于ios 一开始想的是用H5 做个通用的 但是一圈下来 发现兼容性就很难受
好在项目是基于微信公众号 放弃使用普通H5的想法 转战使用微信提供的 JSAPI 录音功能 一下子解决了兼容的问题 微信已经帮忙处理完毕
接下来说一下 注意事项
在使用微信提供的录音功能前
1.首先的是进入微信公众号后台 公众号设置的功能设置 里填写“JS接口安全域名” 一定要记得
2.先引入微信JS 简单的
<script src="http://res.wx.qq.com/open/js/jweixin-1.6.0.js"></script>(https://res.wx.qq.com/open/js/jweixin-1.6.0.js)
3.注册微信配置 使用wx.config() 将要使用的api 填写到jsApiList里面 要记得
注意:签名问题 一是服务端算法问题 二是前端当前地址和请求的地址不同 也会出现签名错误 关于签名问题 其他文章也有说明产生的原因
录音功能 不是立即使用 所以 只需检测是否配置环境成功即可 wx.ready()..等回调方法
.js 配置注册微信环境代码示例
export async function wechatSharefund (columnInfo) {
const data = await wechatConfig(location.href.split('#')[0]) // 返回的微信信息
console.log(data)
if (data.code === 0) {
// 注册
wx.config({
debug: false, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId: data.data.appId, // 必填,公众号的唯一标识
timestamp: data.data.timestamp, // 必填,生成签名的时间戳
nonceStr: data.data.nonceStr, // 必填,生成签名的随机串
signature: data.data.signature, // 必填,签名
jsApiList: [
'updateAppMessageShareData',
'updateTimelineShareData',
'startRecord',
'stopRecord',
'playVoice',
'onVoiceRecordEnd',
'uploadVoice',
'onVoicePlayEnd',
'downloadVoice'
] // 必填,需要使用的JS接口列表
})
// 是否成功
wx.ready(function(res) {
console.log([res, 90])
wx.updateAppMessageShareData({
title: '我是自定义首页!!!!!好友' + store.getters.doctorId,
desc: '自定义描述', // 分享描述
link: 'https://doctor.taiori.com/doctor/hospitalIndex?userParam=' + store.getters.doctorId,
imgUrl: '', // 分享图标
success: function () {
// 设置成功
}
})
wx.updateTimelineShareData({
title: "我是自定义首页!!圈" + store.getters.doctorId,
link: 'https://doctor.taiori.com/doctor/hospitalIndex?userParam=' + store.getters.doctorId,
imgUrl: '',
success: function() {
}
});
});
// 是否支持指定JS接口
wx.checkJsApi({
jsApiList: ['startRecord'], // 需要检测的JS接口列表,所有JS接口列表见附录2,
success: function (res) {
console.log([res, '114'])
store.commit('jsApiList', res)
// 以键值对的形式返回,可用的api值true,不可用为false
// 如:{"checkResult":{"chooseImage":true},"errMsg":"checkJsApi:ok"}
}
})
wx.error(function(res){
console.log(res)
})
}
在使用的地方 引入.j文件
import { wechatSharefund } from '.js'
mounted () {
wechatSharefund()
}
使用简单的
touchstart () {
console.log('开始录音')
if (this.localId) {
return
}
let that = this
wx.startRecord({
success: function(e){
// 开始录音的代码处理
},
cancel: function (e) {
console.log(e)
}
})
},
touchend () {
console.log('结束录音')
if (this.localId) {
return
}
let that = this
clearInterval(this.timer)
wx.stopRecord({
success: function (res) {
// 结束后的代码处理
}
})
}
最后 会涉及到 保存录音 及 上传到自己服务器动作 简单的 使用相对于的API
微信临时素材返回的是speex文件 需要解码成想要的播放MAP3或者wav 前端可直接播放的链接 解码微信有提供方法
uploadVoice() {
let that = this
// 上传到微信为临时素材
wx.uploadVoice({
localId: this.localId, // 需要上传的音频的本地ID,由stopRecord接口获得
isShowProgressTips: 1, // 默认为1,显示进度提示
success: function (res) {
// 获得微信服务端音频ID
var serverId = res.serverId; // 返回音频的服务器端ID
console.log(res)
// 服务端提供接口 传递 音频ID 由服务端处理从微信服务端下载临时素材 存为自己的服务器链接
// http请求方式: GET,https调用 https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID 请求示例(示例为通过curl命令获取多媒体文件)
// curl -I -G "https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID"
// $.ajax({
// url: 'https://api.weixin.qq.com/cgi-bin/media/get?access_token=ACCESS_TOKEN&media_id=MEDIA_ID', // 服务端提供的接口链接
// type: 'GET',
// dataType: "json",
// success: function (data) {
// alert('文件已经保存到自己的服务器');
// },
// error: function (xhr, errorType, error) {
// console.log(error);
// }
// });
}
});
}
示例代码 处理误点击 时长不够 无法监听授权过录音设备问题 和 长按事件 冲突
// 在进入页面时 设置监听 录音自动停止(60秒自动结束录音)
mounted () {
let that = this
/* eslint-disable */
clearInterval(this.timer)
wx.onVoiceRecordEnd({
complete: function (res) {
console.log([res, 105])
clearInterval(that.timer)
that.$toast.clear()
that.loId = res.localId
if (res.localId) {
wx.uploadVoice({
localId: res.localId,
isShowProgressTips: 1,
success: function (resSoso) {
that.localId = resSoso.serverId
that.loadShow = false
that.num = parseInt(that.num)
that.touchstartShow = true
},
})
}
}
})
}
// 长按录音
touchstart (e) {
e.preventDefault() // 阻止长按默认事件
let that = this
console.log('开始')
// 清除当前可能存在的录音
wx.stopRecord()
that.loadShow = false
that.touchstartShow = true
clearInterval(this.timer)
that.voiceLoadShow = false
// window.localStorage.start 判断是否授权过录音设备 未授权过的 即调用wx.startRecord授权并在 成功回调内 立即停止录音 wx.stopRecord() window.localStorage.start = true
if (window.localStorage.start){
if (this.localId||this.loadShow) {
return
}
that.touchstartShow = false
// 获取当前时间 并延迟执行录音事件 防止误点触发录音 在touchend事件中做处理
that.date = new Date().getTime()
that.startRecordTimer = setTimeout(() => {
that.voiceLoadShow = true
that.loadShow = true
that.touchstartShow = false
wx.startRecord({
success: function(e){
console.log(e)
clearInterval(that.timer)
that.timer = setInterval(() => {
that.num += 0.1
}, 100)
},
fail: function (e) {
that.loadShow = false
that.touchstartShow = false
clearInterval(this.timer)
that.$toast.clear()
}
})
}, 200)
} else {
wx.startRecord({
success: function(e){
wx.stopRecord()
window.localStorage.start = true
},
cancel: function(e){
that.$toast('拒绝授权录音')
}
})
}
},
// 结束录音
touchend (e) {
console.log('结束')
let that = this
that.touchstartShow = true
clearInterval(this.timer)
that.$toast.clear()
// 获取当前时间跟长按事件及时保存的时间 对比 是否是长按 误点的话不执行录音(也是延迟录音的原因)
if (new Date().getTime() - that.date < 300) {
that.date = 0
that.loadShow = false
that.touchstartShow = true
clearTimeout(that.startRecordTimer)
return
}
if (that.num > 1) {
wx.stopRecord({
success: function (res) {
console.log([res, 173])
that.loId = res.localId
if (that.num > 0 && res.localId) {
// 录音结束 获取音频ID 并上传微信临时素材
wx.uploadVoice({
localId: res.localId,
isShowProgressTips: 1,
success: function (resSoso) {
// 获取到微信服务端ID 通过ID传本地服务端 下载临时素材到本地服务器
that.localId = resSoso.serverId
that.loadShow = false
that.num = parseInt(that.num)
that.touchstartShow = true
}
})
} else {
that.loadShow = false
that.touchstartShow = true
console.log([that.touchstartShow, 'that.touchstartShow'])
}
}
})
} else {
that.loadShow = false
that.touchstartShow = true
that.$toast('录音时长过短')
}
}
对于H5长按禁止复制弹出菜单 走了挺多歪路 想的就是通过css
*{ // -webkit-touch-callout:none; /*系统默认菜单被禁用*/
// -webkit-user-select:none; /*webkit浏览器*/
// -khtml-user-select:none; /*早期浏览器*/
// -moz-user-select:none; /*火狐*/
// -ms-user-select:none; /*IE10*/
// user-select:none;}
但是还是会出现问题 并不能很好的禁止 最后则通过js 事件的拦截 在长按事件 加上以下代码 就一行 完美解决
e.preventDefault() 阻止事件默认行为
完整组件代码 可直接引用 使用的是 (vant-ui)可以修改
<template>
<div class="container">
<div class="voice-load" v-if="voiceLoadShow">
<img src="@/assets/comment/inmej.gif" alt="">
<div>
说话中...
</div>
</div>
<div class="input-fixed" :style="inputFixedShow?'bottom:0':'bottom:-300px'">
<div class="flex-justify-between flex-item-center">
<div class="xs iconfont" @click="saveClick(false)"></div>
<div class="input-fixed-btns" @click="saveClick(true)">发送</div>
</div>
<div style="color:#ABACAC;padding:16px;padding-bottom:0" v-if="answerName">{{answerName}}</div>
<div class="flex-align-center" style="font-size:14px;padding: 10px 20px 0">
<div v-if="audioShowets">
<img src="@/assets/icon/icon_audios.png" alt="" style="width: 30px;margin-right:10px" @click="audioShow = !audioShow" v-if="audioShow">
<img src="@/assets/icon/icon_keyboard.png" alt="" style="width: 30px;margin-right:10px" @click="audioShow = !audioShow" v-else>
</div>
<van-field
v-model="name"
rows="1"
:autosize="autosize"
type="textarea"
placeholder="请输入..."
:border="false"
class="textarea"
ref="inputs"
v-if="audioShow"
/>
<div v-else class="audis-btn" :style="localId?'justify-content: flex-end;padding-right:10px':''">
<div v-if="localId" class="flex-center">
<div class="flex-align-center voice" :style="'padding-left:' + (num*2 > 120 ? 120 : ( num*3 > 12 ? num*3 : 12)) + 'px'" @click="playVoice()">
{{num}}''
<div class="flex-align-center img-view" style="margin-left:2px;padding-left:3px">
<img :src="item.src" alt="" :style="{width:index === 0 ? '5px' : (index === 1 ? '4px' : '3px'), opacity:item.type?'1':'0'}" class="voice-img1" v-for="(item, index) in voiceImgArr" :key="index">
</div>
</div>
<div class="flex-center" @click="localId='';num=0"><img src="@/assets/icon/icon_cler.png" alt="" style="width:20px;margin-left:20px"></div>
</div>
<button v-else-if="touchstartShow" class="flex-center oll" @touchstart="touchstart">
按住 说话
</button>
<button v-else class="flex-center" @touchend="touchend">
<img src="@/assets/icon/icon_lz.png" alt="" class="opop" style="width:25px">
</button>
</div>
</div>
</div>
<van-overlay :show="inputFixedShow" @click="saveClick(false)"/>
</div>
</template>
<script>
export default {
props: {
show: {
type: Boolean,
default: false
},
audioShowets: {
type: Boolean,
default: false
},
answerType: {},
answerName: {}
},
computed: {
},
watch: {
show (n) {
this.name = ''
this.inputFixedShow = n
this.audioShow = true
this.loadShow = false
this.touchstartShow = true
this.localId = ''
this.num = 0
clearInterval(this.timer)
},
audioShowets (n) {
},
audioShow () {
this.localId = ''
this.num = 0
this.loadShow = false
this.touchstartShow = true
}
},
data () {
return {
voiceLoadShow: false, // 判断显示录音loader true显示
audioShow: true, // 判断输入方式是语音还是文本 true为文本
inputFixedShow: false, // 显示输入框
touchstartShow: true, // 是否处于录音 false为在录音或已经触发录音
loadShow: false, // 是否触发了长按录音
name: '',
localId: '',
autosize: {
maxHeight: '90'
},
num: 0, // 录音的时长
timer: null, // 录音的定时器
imgVoiceNum: 0,
imgTimer: null, // 播放语音的定时器
playShow: true, // 是否已经在播放语音
loId: '',
voiceImgArr: [
{src: require('../assets/image/icon_s.png'), type: true},
{src: require('../assets/image/icon_t.png'), type: true},
{src: require('../assets/image/icon_n.png'), type: true}
],
date: '',
startRecordTimer: null,
typeShow: true// true为触发了长按事件 未触发结束事件
}
},
mounted () {
let that = this
/* eslint-disable */
clearInterval(this.timer)
wx.onVoiceRecordEnd({
complete: function (res) {
clearInterval(that.timer)
that.voiceLoadShow = false
that.loId = res.localId
if (res.localId) {
wx.uploadVoice({
localId: res.localId,
isShowProgressTips: 1,
success: function (resSoso) {
that.localId = resSoso.serverId
that.loadShow = false
that.num = parseInt(that.num)
that.touchstartShow = true
},
})
}
}
})
},
methods: {
/* eslint-disable */
saveClick (type) {
this.inputFixedShow = false
this.$emit('saveChange', {name: this.name, show: type, localId: this.localId, num: this.num})
this.name = ''
},
touchstartss () {
wx.startRecord()
},
touchstart (e) {
e.preventDefault()
let that = this
that.typeShow = true
console.log('开始')
// 停止前面的录音
wx.stopRecord()
that.loadShow = false
that.touchstartShow = true
clearInterval(this.timer)
that.voiceLoadShow = false
// 判断是否授权过
if (window.localStorage.start){
if (this.localId||this.loadShow) {
return
}
that.touchstartShow = false
that.date = new Date().getTime()
that.startRecordTimer = setTimeout(() => {
// 提前显示录制的提示loader·
that.voiceLoadShow = true
that.loadShow = true
that.touchstartShow = false
if (that.typeShow) {
wx.startRecord({
success: function(e){
clearInterval(that.timer)
that.timer = setInterval(() => {
that.num += 0.1
}, 100)
},
fail: function (e) {
that.loadShow = false
that.touchstartShow = true
clearInterval(this.timer)
that.voiceLoadShow = false
}
})
} else {
that.loadShow = false
that.touchstartShow = true
that.voiceLoadShow = false
clearInterval(this.timer)
wx.stopRecord()
}
}, 100)
} else {
wx.startRecord({
success: function(e){
wx.stopRecord()
window.localStorage.start = true
},
cancel: function(e){
that.$toast('拒绝授权录音')
}
})
}
},
touchend (e) {
console.log('结束')
let that = this
that.typeShow = false
that.touchstartShow = true
clearInterval(that.timer)
that.voiceLoadShow = false
if (new Date().getTime() - that.date < 300) {
that.date = 0
that.loadShow = false
that.touchstartShow = true
clearTimeout(that.startRecordTimer)
wx.stopRecord()
return
}
if (that.num > 1) {
wx.stopRecord({
success: function (res) {
that.loId = res.localId
if (that.num > 0 && res.localId) {
wx.uploadVoice({
localId: res.localId,
isShowProgressTips: 1,
success: function (resSoso) {
that.localId = resSoso.serverId
that.loadShow = false
that.num = parseInt(that.num)
that.touchstartShow = true
clearInterval(that.timer)
}
})
} else {
that.loadShow = false
that.touchstartShow = true
}
}
})
} else {
that.loadShow = false
that.touchstartShow = true
that.num = 0
wx.stopRecord()
that.$toast('录音时长过短')
}
},
playVoice () {
let that = this
console.log('我点额~~~')
if (that.playShow) {
wx.playVoice({
localId: that.loId,
success: function (resSoso) {
that.playShow = false
that.voiceImgChange()
wx.onVoicePlayEnd({
success: function (res) {
clearInterval(that.imgTimer)
that.voiceImgArr = [
{src: require('../assets/image/icon_s.png'), type: true},
{src: require('../assets/image/icon_t.png'), type: true},
{src: require('../assets/image/icon_n.png'), type: true}
]
that.playShow = true
that.imgVoiceNum = 0
}
})
}
})
} else {
wx.stopVoice({
localId: that.loId,
success: function (resSoso) {
that.playShow = true
clearInterval(that.imgTimer)
that.voiceImgArr = [
{src: require('../assets/image/icon_s.png'), type: true},
{src: require('../assets/image/icon_t.png'), type: true},
{src: require('../assets/image/icon_n.png'), type: true}
]
that.imgVoiceNum = 0
}
})
}
},
voiceImgChange () {
this.imgTimer = setInterval(() => {
this.imgVoiceNum += 1
switch (this.imgVoiceNum) {
case 1:
this.voiceImgArr = [
{src: require('../assets/image/icon_s.png'), type: false},
{src: require('../assets/image/icon_t.png'), type: false},
{src: require('../assets/image/icon_n.png'), type: true}
]
break
case 2:
this.voiceImgArr = [
{src: require('../assets/image/icon_s.png'), type: false},
{src: require('../assets/image/icon_t.png'), type: true},
{src: require('../assets/image/icon_n.png'), type: true}
]
break
case 3:
this.voiceImgArr = [
{src: require('../assets/image/icon_s.png'), type: true},
{src: require('../assets/image/icon_t.png'), type: true},
{src: require('../assets/image/icon_n.png'), type: true}
]
this.imgVoiceNum = 0
break
default:
break
}
}, 500)
}
}
}
</script>
<style scoped lang="scss">
@import '../../static/css/iconfont.css';
@function px2rem($px){
$rem :37.5;
@return ($px / $rem) + rem;
}
.container{
// -webkit-touch-callout:none; /*系统默认菜单被禁用*/
// -webkit-user-select:none; /*webkit浏览器*/
// -khtml-user-select:none; /*早期浏览器*/
// -moz-user-select:none;/*火狐*/
// -ms-user-select:none; /*IE10*/
// user-select:none;
.flex-item-center{
padding: px2rem(16);
padding-bottom: px2rem(2);
}
.xs{
font-size: px2rem(14);
color: #000;
height: px2rem(28);
line-height: px2rem(28);
width: px2rem(30);
}
.voice-load{
position: fixed;
top: 50%;
left: 50%;
background: rgba(0,0,0,0.8);
border-radius: 5px;
width: 120px;
height: 120px;
margin-left: -60px;
margin-top: -60px;
text-align: center;
color: #fff;
font-size: px2rem(14);
z-index: 102;
img{
width: 80px;
}
}
.input-fixed{
border-radius: px2rem(20) px2rem(20) 0 0;
position: fixed;
bottom: -300px;
left: 0;
width: 100%;
background: #fff;
z-index: 1001;
padding-bottom: px2rem(5);
border-top: 1px solid #f4f4f4;
transition: all 0.51s;
padding-bottom: px2rem(20);
.textarea{
font-size: px2rem(14);
border: 1px solid #e6e6e6;
flex: 1;
}
}
.input-fixed-btns{
width: px2rem(65);
font-size: px2rem(14);
text-align: center;
height: px2rem(28);
line-height: px2rem(28);
background: #0A655A;
border-radius: 5px;
color: #fff;
}
.audis-btn{
text-align: center;
flex: 1;
height: 44px;
line-height: 44px;
display: flex;
align-items: center;
justify-content: center;
border: 1px solid #e6e6e6;
background: #fff;
-webkit-touch-callout: none;
}
.voice{
border-radius: 5px;
background: #0A655A;
color: #fff;
padding: 0 10px;
padding-left: 20px;
height: 30px;
}
.flex-center{
display: flex;
align-items: center;
justify-content: center;
}
.opop{
animation: audio 1000ms infinite;
}
@keyframes audio {
from {
opacity: 1.0;
}
50% {
opacity: 0.4;
}
to {
opacity: 1.0;
}
}
button{
background: #fff;
border: 1px solid transparent;
width: 100%;
height: 100%;
padding: 0;
// border: 1px solid #e6e6e6;
// -webkit-touch-callout:none; /*系统默认菜单被禁用*/
// -webkit-user-select:none; /*webkit浏览器*/
// -khtml-user-select:none; /*早期浏览器*/
// -moz-user-select:none; /*火狐*/
// -ms-user-select:none; /*IE10*/
// user-select:none;
}
input,textarea {
-webkit-user-select:auto; /*webkit浏览器*/
outline: none;
}
.oll{
// -webkit-touch-callout: none !important;
// -webkit-touch-callout:none; /*系统默认菜单被禁用*/
// -webkit-user-select:none; /*webkit浏览器*/
// -khtml-user-select:none; /*早期浏览器*/
// -moz-user-select:none; /*火狐*/
// -ms-user-select:none; /*IE10*/
// user-select:none;
}
}
</style>