Compare commits
4 Commits
Author | SHA1 | Date |
---|---|---|
jia | d638a2378e | |
jia | d1f6af0aee | |
jia | e7b7551a2e | |
weipengfei | b0ea9966c8 |
6
App.vue
6
App.vue
|
@ -2,11 +2,6 @@
|
|||
export default {
|
||||
onLaunch: function(info) {
|
||||
console.log('App Launch');
|
||||
uni.sendHostEvent('log',info, (ret) => {
|
||||
//发送消息成功回调
|
||||
console.log('消息成功app' + JSON.stringify(info));
|
||||
|
||||
});
|
||||
if (info.referrerInfo?.extraData?.uniMP) {
|
||||
uni.setStorageSync('uniMP', info.referrerInfo?.extraData?.uniMP);
|
||||
uni.setStorageSync('APP_token', info.referrerInfo?.extraData?.token);
|
||||
|
@ -29,5 +24,4 @@
|
|||
|
||||
<style>
|
||||
/*每个页面公共css */
|
||||
|
||||
</style>
|
||||
|
|
30
api/api.js
30
api/api.js
|
@ -18,36 +18,6 @@ export function ttWss(data) {
|
|||
return requesta.post(`index/tts`, data);
|
||||
}
|
||||
|
||||
//图片识别
|
||||
export function ttocr(data) {
|
||||
return request.post(`xun_fei/ocr`, data);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// base64 转 二进制流(blob)
|
||||
export function dataURLtoBlob(dataurl) {
|
||||
// console.log(dataurl);
|
||||
// let str = "data:audio/wav;base64," + dataurl.slice(2)
|
||||
// console.log(str);
|
||||
let mime = "audio/wav"
|
||||
// var arr = str.split(","),
|
||||
// mime = arr[0].match(/:(.*?);/),
|
||||
// let bstr = atob(arr[1])
|
||||
let bstr = atob(dataurl)
|
||||
let n = bstr.length
|
||||
let u8arr = new Uint8Array(n);
|
||||
while (n--) {
|
||||
u8arr[n] = bstr.charCodeAt(n);
|
||||
}
|
||||
console.log(u8arr)
|
||||
// return new Blob([u8arr], {
|
||||
// type: mime,
|
||||
// });
|
||||
}
|
||||
|
||||
|
||||
// -防抖
|
||||
export function debounce(fn, wait) {
|
||||
let delay = wait || 500
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
<template>
|
||||
<view class="contentgb">
|
||||
<view class="return_top">
|
||||
<view class="z-bootm-img1" @click="returnbom()">
|
||||
<image src="@/static/icon/fb1.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
<view class="z-bootm-l"></view>
|
||||
<view class="z-bootm-img2" @click="returnbom()">
|
||||
<image src="@/static/icon/fb2.png" mode="aspectFit"></image>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
data() {
|
||||
return {
|
||||
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
returnbom() {
|
||||
uni.sendHostEvent('closeApp', (ret) => {
|
||||
//发送消息成功回调
|
||||
console.log('关闭应用' + JSON.stringify(ret));
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.contentgb {
|
||||
|
||||
width: 166rpx;
|
||||
height: 56rpx;
|
||||
|
||||
.return_top {
|
||||
width: 166rpx;
|
||||
height: 56rpx;
|
||||
background: rgba(255, 255, 255, 0.6);
|
||||
border-radius: 44rpx 44rpx;
|
||||
|
||||
position: relative;
|
||||
|
||||
.z-bootm-img1 {
|
||||
width: 41rpx;
|
||||
height: 13rpx;
|
||||
|
||||
position: absolute;
|
||||
top: 20rpx;
|
||||
left: 25rpx;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.z-bootm-l {
|
||||
width: 1px;
|
||||
height: 32rpx;
|
||||
background: rgba(0, 0, 0, 0.2);
|
||||
margin: auto 0;
|
||||
|
||||
position: absolute;
|
||||
top: 10rpx;
|
||||
left: 85rpx;
|
||||
}
|
||||
|
||||
.z-bootm-img2 {
|
||||
width: 38rpx;
|
||||
height: 34rpx;
|
||||
margin-top: -15rpx;
|
||||
margin-left: -15rpx;
|
||||
position: absolute;
|
||||
right: 25rpx;
|
||||
top: 25rpx;
|
||||
|
||||
image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,444 +0,0 @@
|
|||
<template>
|
||||
<view class="qiqb-ctx" v-bind:class="{'on':show}">
|
||||
<view class="qiqb-ctx-box">
|
||||
<!-- <text @click="show=false"></text> -->
|
||||
<canvas canvas-id="ctx" style="width:330px;height:300px" v-show="img_show"></canvas>
|
||||
</view>
|
||||
<view class="qiqb-ctx-btns u-border-bottom">
|
||||
<view>分享至</view>
|
||||
<view>长按海报可转发分享</view>
|
||||
<view>
|
||||
<!-- <text>微信好友</text><text>分享朋友圈</text> -->
|
||||
<text @click="down">保存图片</text>
|
||||
</view>
|
||||
</view>
|
||||
<view class="qiqb-ctx-close" @click="show=false">取消</view>
|
||||
</view>
|
||||
</template>
|
||||
<!-- https://crmeb-test.shop.lihaink.cn/api/upload/image/field -->
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "share",
|
||||
|
||||
data() {
|
||||
return {
|
||||
show: false,
|
||||
img_show: true,
|
||||
mode: 'img',
|
||||
list: [],
|
||||
pext1: '',
|
||||
pext2: ''
|
||||
}
|
||||
},
|
||||
onLoad() {
|
||||
uni.$on('add', function(res) {
|
||||
console.log(res.listData) // 为 B 页面传过来的值
|
||||
})
|
||||
},
|
||||
mounted() {
|
||||
// console.log(this.list,'222222')
|
||||
},
|
||||
|
||||
|
||||
methods: {
|
||||
//子组件接受参数
|
||||
refresh(index) {
|
||||
this.list = index;
|
||||
this.pext1 = index[index.length - 2].content
|
||||
this.pext2 = index[index.length - 1].content
|
||||
// console.log(this.list)
|
||||
},
|
||||
init() { // 通过opt传参数
|
||||
|
||||
uni.showLoading({
|
||||
title: '生成中'
|
||||
})
|
||||
let self = this
|
||||
let ctx = uni.createCanvasContext('ctx')
|
||||
// 绘制背景 宽度 高度 圆角 颜色
|
||||
self.drawBg(ctx, 330, 300, 8, '#fff')
|
||||
|
||||
// 需要用到的图片提前循环下载好,本地除外
|
||||
uni.downloadFile({
|
||||
url: 'http://banbai.oss-cn-beijing.aliyuncs.com/20230524/96e80fb572e35d4acebdce67dbd41f30.jpg',
|
||||
success: function(e) {
|
||||
let img = e.tempFilePath // 封面图
|
||||
uni.downloadFile({
|
||||
url: 'http://banbai.oss-cn-beijing.aliyuncs.com/20230524/96e80fb572e35d4acebdce67dbd41f30.jpg',
|
||||
success: function(e) {
|
||||
let photo = e.tempFilePath // 头像
|
||||
uni.downloadFile({
|
||||
url: 'http://banbai.oss-cn-beijing.aliyuncs.com/20230606/f0738e1ff432c08b369ca5e4b906a34a.png',
|
||||
success: function(e) {
|
||||
uni.hideLoading()
|
||||
let qrcode = e.tempFilePath // 二维码
|
||||
self.drawTxtOne(ctx, '农业咨询小程序', 220, 35, 100,
|
||||
'#222', 12, false)
|
||||
// 绘制圆角头像 图片地址 距离左边 距离上边 宽度 高度 圆角
|
||||
self.drawImg(ctx,
|
||||
'https://lihai001.oss-cn-chengdu.aliyuncs.com/storage/202310/20231013091310.png',
|
||||
170, 10, 40, 40, 20)
|
||||
self.drawImg(ctx,
|
||||
'https://lihai001.oss-cn-chengdu.aliyuncs.com/storage/202310/20231013091059.png',
|
||||
10, 50, 40, 40, 20)
|
||||
|
||||
self.drawImg(ctx,
|
||||
'https://lihai001.oss-cn-chengdu.aliyuncs.com/storage/202310/20231013091310.png',
|
||||
10, 118, 40, 40, 20)
|
||||
|
||||
// 绘制封面图 图片地址 距离左边 距离上边 宽度 高度 圆角
|
||||
// self.drawImg(ctx, img, 10, 120, 210, 260, 8)
|
||||
|
||||
// 绘制二维码 图片地址 距离左边 距离上边 宽度 高度 圆角
|
||||
self.drawImg(ctx,
|
||||
'https://lihai001.oss-cn-chengdu.aliyuncs.com/storage/202310/ai_qr_20231013091327.jpg',
|
||||
174, 195, 100, 100, 0)
|
||||
|
||||
// 绘制单行文字 图片地址 距离左边 距离上边 宽度 颜色 字号 加粗
|
||||
self.drawTxtOne(ctx, self.pext1, 60, 70, 100,
|
||||
'#222', 12, false)
|
||||
// self.drawTxtTwo(ctx, self.pext1, 60, 64, 80,
|
||||
// '#222', 13)
|
||||
// self.drawTxtTwo(ctx, self.pext1, 60,64,
|
||||
// 190, 20, '#222', 12)
|
||||
// 绘制单行文字 图片地址 距离左边 距离上边 宽度 颜色 字号 加粗
|
||||
// self.drawTxtOne(ctx, '邀请您一起购好物', 60, 80, 100,
|
||||
// '#666', 9, false)
|
||||
|
||||
// 绘制两行文字 图片地址 距离左边 距离上边 宽度 行高 颜色 字号
|
||||
self.drawTxtFour(ctx,
|
||||
self.pext2,
|
||||
60, 120,
|
||||
190, 20, '#222', 12)
|
||||
|
||||
// 绘制单行文字 图片地址 距离左边 距离上边 宽度 颜色 字号 加粗
|
||||
self.drawTxtOne(ctx, '下载二维码', 15, 250, 100,
|
||||
'#666', 11, false)
|
||||
|
||||
// 绘制单行文字 图片地址 距离左边 距离上边 宽度 颜色 字号 加粗
|
||||
// self.drawTxtOne(ctx, '今日特价:¥', 15, 350, 100,
|
||||
// '#ED3327', 11, false)
|
||||
|
||||
// 绘制单行文字 图片地址 距离左边 距离上边 宽度 颜色 字号 加粗
|
||||
// self.drawTxtOne(ctx, '86.00', 82, 350, 100,
|
||||
// '#ED3327', 11, true)
|
||||
|
||||
// 绘制删除文字 图片地址 距离左边 距离上边 行高 颜色 字号 加粗
|
||||
// self.drawTxtDel(ctx, '原价:20', 15, 370, 4,
|
||||
// '#818181', 10, false)
|
||||
|
||||
// 结束绘制
|
||||
ctx.draw()
|
||||
self.show = true
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
down() {
|
||||
uni.showLoading({
|
||||
title: '保存中'
|
||||
})
|
||||
uni.canvasToTempFilePath({
|
||||
canvasId: 'ctx',
|
||||
quality: 1,
|
||||
success: (res) => {
|
||||
uni.hideLoading()
|
||||
console.log(res)
|
||||
this.$emit('down', res)
|
||||
this.show = false
|
||||
// 保存到相册 自定义实现
|
||||
uni.saveImageToPhotosAlbum({
|
||||
filePath: res.tempFilePath,
|
||||
success: (res) => {
|
||||
console.log('保存')
|
||||
uni.showModal({
|
||||
title: '保存成功'
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
},
|
||||
// 绘制背景 宽度 高度 圆角 颜色
|
||||
drawBg(ctx, w, h, r, color) {
|
||||
let x = 0
|
||||
let y = 0
|
||||
ctx.setFillStyle(color)
|
||||
ctx.save()
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(x + r, y)
|
||||
ctx.arcTo(x + w, y, x + w, y + h, r)
|
||||
ctx.arcTo(x + w, y + h, x, y + h, r)
|
||||
ctx.arcTo(x, y + h, x, y, r)
|
||||
ctx.arcTo(x, y, x + w, y, r)
|
||||
ctx.closePath()
|
||||
ctx.fill()
|
||||
},
|
||||
// 绘制图片 图片地址 距离左边 距离上边 宽度 高度 圆角
|
||||
drawImg(ctx, img, x, y, w, h, r) {
|
||||
let self = this
|
||||
ctx.save()
|
||||
ctx.beginPath()
|
||||
ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5)
|
||||
ctx.arc(x + w - r, y + r, r, Math.PI * 1.5, Math.PI * 2)
|
||||
ctx.arc(x + w - r, y + h - r, r, 0, Math.PI * 0.5)
|
||||
ctx.arc(x + r, y + h - r, r, Math.PI * 0.5, Math.PI)
|
||||
ctx.clip()
|
||||
ctx.drawImage(img, x, y, w, h)
|
||||
ctx.restore()
|
||||
},
|
||||
// 绘制单行文字 图片地址 距离左边 距离上边 宽度 颜色 字号 加粗
|
||||
drawTxtOne(ctx, text, x, y, maxWidth, color, size, bold) {
|
||||
ctx.save()
|
||||
ctx.beginPath()
|
||||
let chr = text.split("")
|
||||
let temp = ""
|
||||
let row = []
|
||||
ctx.setFontSize(size)
|
||||
ctx.setFillStyle(color)
|
||||
for (let a = 0; a < chr.length; a++) {
|
||||
if (ctx.measureText(temp).width < maxWidth) {
|
||||
temp += chr[a]
|
||||
}
|
||||
}
|
||||
row.push(temp)
|
||||
if (ctx.measureText(temp).width > maxWidth) row[0] = row[0] + '...'
|
||||
|
||||
for (var b = 0; b < row.length; b++) {
|
||||
ctx.fillStyle = color
|
||||
if (bold) {
|
||||
ctx.font = 'normal bold ' + size + 'px sans-serif'
|
||||
} else {
|
||||
ctx.font = 'normal ' + size + 'px sans-serif'
|
||||
}
|
||||
ctx.fillText(row[b], x, y)
|
||||
}
|
||||
},
|
||||
// 绘制两行文字 图片地址 距离左边 距离上边 宽度 行高 颜色 字号
|
||||
drawTxtTwo(ctx, text, x, y, maxWidth, lineHeight, color, size) {
|
||||
ctx.save()
|
||||
ctx.beginPath()
|
||||
let chr = text.split("")
|
||||
let temp = ""
|
||||
let row = []
|
||||
ctx.setFontSize(size)
|
||||
ctx.setFillStyle(color)
|
||||
for (let a = 0; a < chr.length; a++) {
|
||||
if (ctx.measureText(temp).width < maxWidth) {
|
||||
temp += chr[a];
|
||||
} else {
|
||||
a--
|
||||
row.push(temp)
|
||||
temp = ""
|
||||
}
|
||||
}
|
||||
row.push(temp)
|
||||
if (row.length > 2) {
|
||||
let rowCut = row.slice(0, 2)
|
||||
let rowPart = rowCut[1]
|
||||
let test = ""
|
||||
let empty = []
|
||||
for (var a = 0; a < rowPart.length; a++) {
|
||||
if (ctx.measureText(test).width < maxWidth) {
|
||||
test += rowPart[a]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
empty.push(test)
|
||||
var group = empty[0] + "..."
|
||||
rowCut.splice(1, 1, group)
|
||||
row = rowCut
|
||||
}
|
||||
for (var b = 0; b < row.length; b++) {
|
||||
ctx.fillStyle = color
|
||||
ctx.font = 'normal ' + size + 'px sans-serif'
|
||||
ctx.fillText(row[b], x, y + b * lineHeight, 300)
|
||||
}
|
||||
},
|
||||
drawTxtFour(ctx, text, x, y, maxWidth, lineHeight, color, size) {
|
||||
ctx.save()
|
||||
ctx.beginPath()
|
||||
let chr = text.split("")
|
||||
let temp = ""
|
||||
let row = []
|
||||
ctx.setFontSize(size)
|
||||
ctx.setFillStyle(color)
|
||||
for (let a = 0; a < chr.length; a++) {
|
||||
if (ctx.measureText(temp).width < maxWidth) {
|
||||
temp += chr[a];
|
||||
} else {
|
||||
a--
|
||||
row.push(temp)
|
||||
temp = ""
|
||||
}
|
||||
}
|
||||
row.push(temp)
|
||||
if (row.length > 4) {
|
||||
let rowCut = row.slice(0, 4)
|
||||
let rowPart = rowCut[1]
|
||||
let test = ""
|
||||
let empty = []
|
||||
for (var a = 0; a < rowPart.length; a++) {
|
||||
if (ctx.measureText(test).width < maxWidth) {
|
||||
test += rowPart[a]
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
empty.push(test)
|
||||
var group = empty[0] + "..."
|
||||
rowCut.splice(1, 1, group)
|
||||
row = rowCut
|
||||
}
|
||||
for (var b = 0; b < row.length; b++) {
|
||||
ctx.fillStyle = color
|
||||
ctx.font = 'normal ' + size + 'px sans-serif'
|
||||
ctx.fillText(row[b], x, y + b * lineHeight, 300)
|
||||
}
|
||||
},
|
||||
// 绘制删除线文字 图片地址 距离左边 距离上边 行高 颜色 字号 加粗
|
||||
drawTxtDel(ctx, txt, x, y, h, color, size) {
|
||||
ctx.fillStyle = color
|
||||
ctx.font = 'normal ' + size + 'px sans-serif'
|
||||
ctx.fillText(txt, x, y)
|
||||
|
||||
ctx.beginPath()
|
||||
let width = ctx.measureText(txt).width;
|
||||
ctx.rect(x, y - h, width, 1)
|
||||
ctx.fillStyle = color
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
.qiqb-ctx {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: -1;
|
||||
display: flex;
|
||||
background: rgba(0, 0, 0, .6);
|
||||
opacity: 0;
|
||||
transition: all .3s;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: flex-end
|
||||
}
|
||||
|
||||
.qiqb-ctx.on {
|
||||
z-index: 2000;
|
||||
opacity: 1
|
||||
}
|
||||
|
||||
.qiqb-ctx-box {
|
||||
position: relative;
|
||||
display: flex;
|
||||
padding: 30upx 0 40upx;
|
||||
width: 750upx;
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.qiqb-ctx-box text {
|
||||
position: absolute;
|
||||
top: -40upx;
|
||||
right: 145upx;
|
||||
display: inline-flex;
|
||||
width: 47upx;
|
||||
height: 47upx;
|
||||
background: url('img/close.png');
|
||||
background-size: 100% 100%
|
||||
}
|
||||
|
||||
.qiqb-ctx-btns {
|
||||
display: flex;
|
||||
width: 750upx;
|
||||
height: 330upx;
|
||||
border-radius: 20upx 20upx 0 0;
|
||||
background: #fff;
|
||||
flex-direction: row;
|
||||
flex-direction: column;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.qiqb-ctx-btns view:nth-child(1) {
|
||||
display: flex;
|
||||
padding-top: 10upx;
|
||||
height: 80upx;
|
||||
font-weight: 700;
|
||||
font-size: 30upx;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.qiqb-ctx-btns view:nth-child(2) {
|
||||
height: 80upx;
|
||||
color: #666;
|
||||
font-size: 24upx
|
||||
}
|
||||
|
||||
.qiqb-ctx-btns view:nth-child(3) {
|
||||
display: flex;
|
||||
padding-bottom: 30upx;
|
||||
font-size: 26upx;
|
||||
flex-direction: row;
|
||||
align-items: center
|
||||
}
|
||||
|
||||
.qiqb-ctx-btns view:nth-child(3) text {
|
||||
display: flex;
|
||||
width: 250upx;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center
|
||||
}
|
||||
|
||||
.qiqb-ctx-btns view:nth-child(3) text:nth-child(1)::before {
|
||||
display: inline-flex;
|
||||
margin-bottom: 20upx;
|
||||
width: 86upx;
|
||||
height: 86upx;
|
||||
background: url('img/3.png');
|
||||
background-size: 100% 100%;
|
||||
content: ''
|
||||
}
|
||||
|
||||
.qiqb-ctx-btns view:nth-child(3) text:nth-child(2)::before {
|
||||
display: inline-flex;
|
||||
margin-bottom: 20upx;
|
||||
width: 86upx;
|
||||
height: 86upx;
|
||||
background: url('img/1.png');
|
||||
background-size: 100% 100%;
|
||||
content: ''
|
||||
}
|
||||
|
||||
.qiqb-ctx-btns view:nth-child(3) text:nth-child(3)::before {
|
||||
display: inline-flex;
|
||||
margin-bottom: 20upx;
|
||||
width: 86upx;
|
||||
height: 86upx;
|
||||
background: url('img/2.png');
|
||||
background-size: 100% 100%;
|
||||
content: ''
|
||||
}
|
||||
|
||||
.qiqb-ctx-close {
|
||||
display: flex;
|
||||
padding-bottom: 50upx;
|
||||
width: 100%;
|
||||
height: 150upx;
|
||||
background: #fff;
|
||||
font-size: 32upx;
|
||||
align-items: center;
|
||||
justify-content: center
|
||||
}
|
||||
</style>
|
|
@ -39,7 +39,7 @@ function baseRequest(url, method, data, {
|
|||
data: data || {},
|
||||
success: (res) => {
|
||||
// #ifdef APP-PLUS
|
||||
|
||||
// console.log('app', Url + '/api/' + url, res.data);
|
||||
|
||||
// #endif
|
||||
if (noVerify)
|
||||
|
|
|
@ -40,7 +40,7 @@ function baseRequest(url, method, data, {
|
|||
success: (res) => {
|
||||
|
||||
// #ifdef APP-PLUS
|
||||
console.log('app', Url + '/api/' + url, res.data);
|
||||
// console.log('app', Url + '/api/' + url, res.data);
|
||||
|
||||
// #endif
|
||||
if (noVerify)
|
||||
|
|
|
@ -17,34 +17,26 @@
|
|||
"delay" : 0
|
||||
},
|
||||
/* 模块配置 */
|
||||
"modules" : {
|
||||
"Record" : {},
|
||||
"Speech" : {},
|
||||
"Camera" : {}
|
||||
},
|
||||
"modules" : {},
|
||||
/* 应用发布信息 */
|
||||
"distribute" : {
|
||||
/* android打包配置 */
|
||||
"android" : {
|
||||
"permissions" : [
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.INTERNET\"/>",
|
||||
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
|
||||
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
|
||||
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\"/>",
|
||||
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
|
||||
"<uses-feature android:name=\"android.hardware.camera\"/>",
|
||||
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
|
||||
],
|
||||
"abiFilters" : [ "armeabi-v7a", "arm64-v8a", "x86" ]
|
||||
|
@ -55,25 +47,7 @@
|
|||
},
|
||||
/* SDK配置 */
|
||||
"sdkConfigs" : {
|
||||
"ad" : {},
|
||||
"speech" : {},
|
||||
"share" : {}
|
||||
}
|
||||
},
|
||||
"nativePlugins" : {
|
||||
"YL-SpeechRecognition" : {
|
||||
"__plugin_info__" : {
|
||||
"name" : "YL-SpeechRecognition-语音合成,语音听写,基于科大讯飞,双端 - [试用版,仅用于自定义调试基座]",
|
||||
"description" : "语音合成,语音听写,基于科大讯飞,双端(QQ:453503875,微信同)",
|
||||
"platforms" : "Android,iOS",
|
||||
"url" : "https://ext.dcloud.net.cn/plugin?id=9512",
|
||||
"android_package_name" : "",
|
||||
"ios_bundle_id" : "",
|
||||
"isCloud" : true,
|
||||
"bought" : 0,
|
||||
"pid" : "9512",
|
||||
"parameters" : {}
|
||||
}
|
||||
"ad" : {}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -81,7 +55,7 @@
|
|||
"quickapp" : {},
|
||||
/* 小程序特有相关 */
|
||||
"mp-weixin" : {
|
||||
"appid" : "wx3267f87bc81f2890",
|
||||
"appid" : "",
|
||||
"setting" : {
|
||||
"urlCheck" : false
|
||||
},
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
{
|
||||
"path": "pages/index/index",
|
||||
"style": {
|
||||
"navigationBarTitleText": "教育咨询",
|
||||
"navigationBarTitleText": "农业咨询",
|
||||
"navigationStyle": "custom",
|
||||
"bounce": "none"
|
||||
// "softinputMode": "adjustResize"
|
||||
|
|
|
@ -5,10 +5,9 @@
|
|||
<view class="conten-top">
|
||||
<view class="" style="position: absolute;left: 30rpx;"><i class="iconfont icon-xiangzuo"
|
||||
style="font-size: 40rpx;text-align: left;"></i></view>
|
||||
<view class="" style="font-size: 30rpx;margin-top: 10rpx;">
|
||||
情感咨询
|
||||
<view class="" style="font-size: 30rpx;">
|
||||
农业咨询
|
||||
</view>
|
||||
<returntop class="container-retun"/>
|
||||
</view>
|
||||
<view style="padding-top:var(--status-bar-height);padding-bottom: 30rpx; margin-bottom: 30rpx;"></view>
|
||||
|
||||
|
@ -23,13 +22,13 @@
|
|||
您好!
|
||||
</view>
|
||||
<view class="talk-list-con-title1">
|
||||
您可以和我说任何有关情感的问题。<br />
|
||||
您可以和我说任何有关农业的问题。<br />
|
||||
例如下面的问题,赶快试试吧!
|
||||
</view>
|
||||
<view class="talk-list-con-title2">
|
||||
<text style="margin-top: 15rpx;"> 下方输入:</text>
|
||||
<view class="" @click="sendDetail('怎么保持心情愉快')">
|
||||
怎么保持心情愉快
|
||||
<view class="" @click="sendDetail('冬季怎么保存蔬菜')">
|
||||
冬季怎么保存蔬菜
|
||||
<image src="../../static/icon/airight.png" mode=""></image>
|
||||
</view>
|
||||
</view>
|
||||
|
@ -248,20 +247,19 @@
|
|||
import qiqbshare from "@/components/qiqb-share/qiqb-share.vue"
|
||||
|
||||
|
||||
import returntop from '@/components/content-top/index.vue'
|
||||
const recorderManager = uni.getRecorderManager()
|
||||
const innerAudioContext = uni.createInnerAudioContext()
|
||||
export default {
|
||||
components: {
|
||||
'bing-math': BingMath,
|
||||
'qiqbshare': qiqbshare,
|
||||
returntop
|
||||
|
||||
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
wssType: 'qinggan', // 网络连接类型
|
||||
avatarType: "/static/icon/qg.png", // AI头像类型
|
||||
wssType: 'nongye', // 网络连接类型
|
||||
avatarType: "/static/icon/ny.png", // AI头像类型
|
||||
talkList: [],
|
||||
ajax: {
|
||||
rows: 15, //每页数量
|
||||
|
@ -1603,14 +1601,8 @@ returntop
|
|||
top: 0;
|
||||
position: fixed;
|
||||
z-index: 10;
|
||||
.container-retun{
|
||||
position: absolute;
|
||||
right: 20rpx;
|
||||
padding-top: var(--status-bar-height);
|
||||
top: 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.popup_box {
|
||||
height: 100vh;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,168 @@
|
|||
.h1 {
|
||||
text-align: center;
|
||||
}
|
||||
body{
|
||||
background-color: transparent !important;
|
||||
}
|
||||
.voice-box {
|
||||
box-sizing : border-box;
|
||||
display : flex;
|
||||
justify-content: center;
|
||||
margin : 50px auto;
|
||||
padding : 50px;
|
||||
width : 60%;
|
||||
|
||||
border-radius : 3px;
|
||||
}
|
||||
|
||||
.voice-box input {
|
||||
box-sizing : border-box;
|
||||
padding : 10px;
|
||||
width : 70%;
|
||||
height : 50px;
|
||||
font-size : 18px;
|
||||
color : #187cff;
|
||||
border : 1px solid #187cff;
|
||||
border-radius: 3px 0px 0px 3px;
|
||||
}
|
||||
|
||||
.voice-box button {
|
||||
width : 30%;
|
||||
height : 50px;
|
||||
line-height : 50px;
|
||||
font-size : 18px;
|
||||
color : white;
|
||||
background : #187cff;
|
||||
border : none;
|
||||
border-radius: 0px 3px 3px 0px;
|
||||
cursor : pointer;
|
||||
}
|
||||
|
||||
.voice-box button::before {
|
||||
content : "";
|
||||
display : inline-block;
|
||||
width : 23px;
|
||||
height : 26px;
|
||||
vertical-align : middle;
|
||||
background : url(../img/voice.png) no-repeat;
|
||||
background-size: contain;
|
||||
|
||||
}
|
||||
|
||||
.fixed-box {
|
||||
box-sizing: border-box;
|
||||
display : none;
|
||||
position : fixed;
|
||||
top : 0px;
|
||||
right : 0px;
|
||||
bottom : 0px;
|
||||
left : 0px;
|
||||
border : .04rem solid #e0e7ff;
|
||||
background: rgba(16, 22, 62, .2);
|
||||
}
|
||||
|
||||
.fixed-close {
|
||||
position : absolute;
|
||||
top : 0px;
|
||||
right : 0px;
|
||||
padding : 24px;
|
||||
width : 20px;
|
||||
height : 20px;
|
||||
border : none;
|
||||
background : url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeCAYAAAA7MK6iAAABeklEQVRIibXWsWoVQRQA0LObgGCTFFr6env9h/yETSz0tclrBEUJWhjzAqkV/A9/QrC2CtgIYqk2DwvdMExm991ZJrdaZoY53Bnm3u0Wy80BPuI21jjRPjqc4il+4FG3WG4ucS9ZtMaqMfoej5OxLz3uZAuPcXaDKNzt/TuCPFrgYyi87fEK543xKfQFLvoEaYVvQ19DnwxO4euWaA5P4UcBPIzCbmHRcYLleDqfox9wGEG5nnGKRzOvRilnnOIDluPD/BT6HG/GNp+CI/j+HDQCR/BqlPE7LuGlO5+F1sARPIzWwh32Jub3K/YKw1PFYYgV3rWEI2iKh2r7NnhbGZzdWKaeU03tzZ/W8ARH/2TGMq5BZ7XUElzVZebiOTzU3ho0RUqts4in8Kwuk8Uqig/wti4TQSP41XgfQMNlMIBf9fPd/x8t0RTn+h/LEX52i+XmN241RtM4K+Dfe3y7QZTysV/u7D18+RkP8AfPxPpubXzCL9zHVzz5C8ecbD1n3xuUAAAAAElFTkSuQmCC) no-repeat center center;
|
||||
background-size: 20px;
|
||||
transition : all .5s;
|
||||
cursor : pointer;
|
||||
outline : none;
|
||||
}
|
||||
|
||||
.fixed-close:hover {
|
||||
transform : rotate(270deg);
|
||||
-webkit-transform: rotate(270deg);
|
||||
}
|
||||
|
||||
|
||||
.fixed-main {
|
||||
box-sizing : border-box;
|
||||
position : absolute;
|
||||
left : 20%;
|
||||
bottom : 0px;
|
||||
padding : 30px;
|
||||
width : 60%;
|
||||
height : 240px;
|
||||
font-size : 18px;
|
||||
background : white;
|
||||
border : 2px solid #e0e7ff;
|
||||
border-radius: 5px 5px 0px 0px;
|
||||
overflow-y : auto;
|
||||
}
|
||||
|
||||
.fixed-icon {
|
||||
box-sizing : border-box;
|
||||
display : flex;
|
||||
justify-content: center;
|
||||
align-items : center;
|
||||
position : absolute;
|
||||
top : 50%;
|
||||
left : 50%;
|
||||
margin-top : -50px;
|
||||
margin-left : -50px;
|
||||
width : 100px;
|
||||
height : 100px;
|
||||
background : linear-gradient(115deg, #56d8e4 5%, #9f01ea 95%);
|
||||
border : 1px solid #e0e7ff;
|
||||
border-radius : 50%;
|
||||
}
|
||||
|
||||
.fixed-icon::before {
|
||||
content : "";
|
||||
position : absolute;
|
||||
display : inline-block;
|
||||
width : 100px;
|
||||
height : 100px;
|
||||
border : 1px solid #9f01ea;
|
||||
box-shadow : 0px 0px 6px 0px #9f01ea;
|
||||
border-radius: 50%;
|
||||
animation : move 1.5s infinite;
|
||||
}
|
||||
|
||||
.fixed-icon::after {
|
||||
content : "";
|
||||
position : absolute;
|
||||
display : inline-block;
|
||||
width : 100px;
|
||||
height : 100px;
|
||||
border : 1px solid #56d8e4;
|
||||
box-shadow : 0px 0px 6px 0px #56d8e4;
|
||||
border-radius: 50%;
|
||||
animation : move 1.5s .5s infinite;
|
||||
|
||||
}
|
||||
|
||||
.fixed-icon img {
|
||||
width : 30px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
@keyframes move {
|
||||
0% {
|
||||
opacity : 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity : 0;
|
||||
transform: scale(2);
|
||||
}
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
width : 6px;
|
||||
height : 6px;
|
||||
background-color: #f9f9f9;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
border-radius : 6px;
|
||||
background-color: #187cff;
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 1.2 MiB |
Binary file not shown.
After Width: | Height: | Size: 85 KiB |
Binary file not shown.
After Width: | Height: | Size: 2.0 KiB |
|
@ -0,0 +1,107 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport"
|
||||
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum=1.0, user-scalable=no shrink-to-fit=no" />
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
|
||||
<link rel="stylesheet" href="./css/base.css" />
|
||||
<!-- <script async src="https://hm.baidu.com/hm.js?85fad12bb9a6dab448f4eff0a19299a5"></script> -->
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
|
||||
<section class="voice-box">
|
||||
<input type="search" name="voice" id="voice-txt" />
|
||||
<button id="start-btn">开始识别</button>
|
||||
</section>
|
||||
|
||||
<section class="fixed-box" id="fixed-box">
|
||||
<div class="fixed-main">
|
||||
<button class="fixed-close" id="close-btn"></button>
|
||||
<div id="fixed-txt">Hello! 请说出你想说话。。。!</div>
|
||||
<div class="fixed-icon">
|
||||
<img src="./img/voice.png" alt="" />
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<script type="text/javascript" src="https://js.cdn.aliyun.dcloud.net.cn/dev/uni-app/uni.webview.1.5.2.js">
|
||||
</script>
|
||||
<script src="./js/crypto-js.min.js"></script>
|
||||
<script src="./js/voice.js"></script>
|
||||
|
||||
<script>
|
||||
window.onload = function() {
|
||||
const voiceTxt = document.querySelector('#voice-txt');
|
||||
const startBtn = document.querySelector('#start-btn');
|
||||
const fixedBox = document.querySelector('#fixed-box');
|
||||
const fixedTxt = document.querySelector('#fixed-txt');
|
||||
const closeBtn = document.querySelector('#close-btn');
|
||||
let times = null;
|
||||
|
||||
|
||||
// 实例化迅飞语音听写(流式版)WebAPI
|
||||
const voice = new Voice({
|
||||
|
||||
// 服务接口认证信息 注:apiKey 和 apiSecret 的长度都差不多,请要填错哦,!
|
||||
appId: '2eda6c2e',
|
||||
apiSecret: 'MDEyMzE5YTc5YmQ5NjMwOTU1MWY4N2Y2',
|
||||
apiKey: '12ec1f9d113932575fc4b114a2f60ffd',
|
||||
// 注:要获取以上3个参数,请到迅飞开放平台:https://www.xfyun.cn/services/voicedictation 【注:这是我的迅飞语音听写(流式版)每天服务量500(也就是调500次),如果你需求里大请购买服务量:https://www.xfyun.cn/services/voicedictation?target=price】
|
||||
|
||||
onWillStatusChange: function(oldStatus, newStatus) {
|
||||
//可以在这里进行页面中一些交互逻辑处理:注:倒计时(语音听写只有60s),录音的动画,按钮交互等!
|
||||
fixedBox.style.display = 'block';
|
||||
},
|
||||
onTextChange: function(text) {
|
||||
//监听识别结果的变化
|
||||
voiceTxt.value = text;
|
||||
fixedTxt.innerText = text;
|
||||
console.log(text,'11111')
|
||||
// 3秒钟内没有说话,就自动关闭
|
||||
if (text) {
|
||||
setTimeout(() => {
|
||||
voice.stop();
|
||||
uni.postMessage({
|
||||
data: text
|
||||
});
|
||||
fixedBox.style.display = 'none';
|
||||
}, 3000);
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
addUniEvenPassthrough()
|
||||
addcloEvenPassthrough()
|
||||
|
||||
function addUniEvenPassthrough() {
|
||||
window.uniEvent = function(info) {
|
||||
console.log('addUniEvenPassthrough')
|
||||
voice.start();
|
||||
}
|
||||
}
|
||||
|
||||
function addcloEvenPassthrough() {
|
||||
window.closeEvent = function(info) {
|
||||
console.log('addcloEvenPassthrough')
|
||||
voice.stop();
|
||||
fixedBox.style.display = 'none';
|
||||
}
|
||||
}
|
||||
// 开始识别
|
||||
startBtn['onclick'] = function() {
|
||||
voice.start();
|
||||
};
|
||||
|
||||
// 关闭识别
|
||||
closeBtn['onclick'] = function() {
|
||||
voice.stop();
|
||||
fixedBox.style.display = 'none';
|
||||
};
|
||||
};
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
File diff suppressed because one or more lines are too long
|
@ -0,0 +1,48 @@
|
|||
;(function () {
|
||||
let self = this
|
||||
self.onmessage = function (e) {
|
||||
transAudioData.transcode(e.data)
|
||||
}
|
||||
|
||||
let transAudioData = {
|
||||
transcode(audioData) {
|
||||
|
||||
let output = transAudioData.to16kHz(audioData)
|
||||
output = transAudioData.to16BitPCM(output)
|
||||
output = Array.from(new Uint8Array(output.buffer))
|
||||
self.postMessage(output)
|
||||
// return output
|
||||
},
|
||||
|
||||
to16kHz(audioData) {
|
||||
var data = new Float32Array(audioData)
|
||||
var fitCount = Math.round(data.length * (16000 / 44100))
|
||||
var newData = new Float32Array(fitCount)
|
||||
var springFactor = (data.length - 1) / (fitCount - 1)
|
||||
newData[0] = data[0]
|
||||
for (let i = 1; i < fitCount - 1; i++) {
|
||||
var tmp = i * springFactor
|
||||
var before = Math.floor(tmp).toFixed()
|
||||
var after = Math.ceil(tmp).toFixed()
|
||||
var atPoint = tmp - before
|
||||
newData[i] = data[before] + (data[after] - data[before]) * atPoint
|
||||
}
|
||||
newData[fitCount - 1] = data[data.length - 1]
|
||||
return newData
|
||||
},
|
||||
|
||||
to16BitPCM(input) {
|
||||
var dataLength = input.length * (16 / 8)
|
||||
var dataBuffer = new ArrayBuffer(dataLength)
|
||||
var dataView = new DataView(dataBuffer)
|
||||
var offset = 0
|
||||
for (var i = 0; i < input.length; i++, offset += 2) {
|
||||
var s = Math.max(-1, Math.min(1, input[i]))
|
||||
dataView.setInt16(offset, s < 0 ? s * 0x8000 : s * 0x7fff, true)
|
||||
}
|
||||
return dataView
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,357 @@
|
|||
; (function (window, voice) {
|
||||
"use strict";
|
||||
if (typeof define === 'function' && define.amd) {
|
||||
define(voice);
|
||||
} else if (typeof exports === 'object') {
|
||||
module.exports = voice();
|
||||
} else {
|
||||
window.Voice = voice();
|
||||
};
|
||||
}(typeof window !== "undefined" ? window : this, () => {
|
||||
"use strict";
|
||||
return class IatRecorder {
|
||||
constructor(opts = {}) {
|
||||
// 服务接口认证信息(语音听写(流式版)WebAPI)
|
||||
this.appId = opts.appId || '';
|
||||
this.apiKey = opts.apiKey || '';
|
||||
this.apiSecret = opts.apiSecret || '';
|
||||
// 识别监听方法
|
||||
this.onTextChange = opts.onTextChange || Function();
|
||||
this.onWillStatusChange = opts.onWillStatusChange || Function();
|
||||
// 方言/语种
|
||||
this.status = 'null'
|
||||
this.language = opts.language || 'zh_cn'
|
||||
this.accent = opts.accent || 'mandarin';
|
||||
// 流媒体
|
||||
this.streamRef = [];
|
||||
// 记录音频数据
|
||||
this.audioData = [];
|
||||
// 记录听写结果
|
||||
this.resultText = '';
|
||||
// wpgs下的听写结果需要中间状态辅助记录
|
||||
this.resultTextTemp = '';
|
||||
// 音频数据多线程
|
||||
this.init();
|
||||
};
|
||||
// WebSocket请求地址鉴权
|
||||
getWebSocketUrl() {
|
||||
return new Promise((resolve, reject) => {
|
||||
// 请求地址根据语种不同变化
|
||||
try {
|
||||
const CryptoJS = require('crypto-js');
|
||||
let url = 'wss://iat-api.xfyun.cn/v2/iat',
|
||||
host = 'iat-api.xfyun.cn',
|
||||
date = new Date().toGMTString(),
|
||||
algorithm = 'hmac-sha256',
|
||||
headers = 'host date request-line',
|
||||
signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`,
|
||||
signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.apiSecret),
|
||||
signature = CryptoJS.enc.Base64.stringify(signatureSha),
|
||||
authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`,
|
||||
authorization = btoa(authorizationOrigin);
|
||||
resolve(`${url}?authorization=${authorization}&date=${date}&host=${host}`);
|
||||
} catch (error) {
|
||||
let url = 'wss://iat-api.xfyun.cn/v2/iat',
|
||||
host = 'iat-api.xfyun.cn',
|
||||
date = new Date().toGMTString(),
|
||||
algorithm = 'hmac-sha256',
|
||||
headers = 'host date request-line',
|
||||
signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/iat HTTP/1.1`,
|
||||
signatureSha = CryptoJS.HmacSHA256(signatureOrigin, this.apiSecret),
|
||||
signature = CryptoJS.enc.Base64.stringify(signatureSha),
|
||||
authorizationOrigin = `api_key="${this.apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`,
|
||||
authorization = btoa(authorizationOrigin);
|
||||
resolve(`${url}?authorization=${authorization}&date=${date}&host=${host}`);
|
||||
};
|
||||
});
|
||||
};
|
||||
// 操作初始化
|
||||
init() {
|
||||
const self = this;
|
||||
try {
|
||||
if (!self.appId || !self.apiKey || !self.apiSecret) {
|
||||
alert('请正确配置【迅飞语音听写(流式版)WebAPI】服务接口认证信息!');
|
||||
} else {
|
||||
self.webWorker = new Worker('./js/transcode.worker.js');
|
||||
self.webWorker.onmessage = function (event) {
|
||||
|
||||
self.audioData.push(...event.data);
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
alert('对不起:请在服务器环境下运行!');
|
||||
console.error('请在服务器如:WAMP、XAMPP、Phpstudy、http-server、WebServer等环境中运行!', error);
|
||||
};
|
||||
console.log("%c ❤️使用说明:http://www.muguilin.com/blog/info/609bafc50d572b3fd79b058f", "font-size:32px; color:blue; font-weight: bold;");
|
||||
};
|
||||
// 修改录音听写状态
|
||||
setStatus(status) {
|
||||
this.onWillStatusChange && this.status !== status && this.onWillStatusChange(this.status, status);
|
||||
this.status = status;
|
||||
};
|
||||
// 设置识别结果内容
|
||||
setResultText({ resultText, resultTextTemp } = {}) {
|
||||
this.onTextChange && this.onTextChange(resultTextTemp || resultText || '');
|
||||
resultText !== undefined && (this.resultText = resultText);
|
||||
resultTextTemp !== undefined && (this.resultTextTemp = resultTextTemp);
|
||||
};
|
||||
// 修改听写参数
|
||||
setParams({ language, accent } = {}) {
|
||||
language && (this.language = language)
|
||||
accent && (this.accent = accent)
|
||||
};
|
||||
// 对处理后的音频数据进行base64编码,
|
||||
toBase64(buffer) {
|
||||
let binary = '';
|
||||
let bytes = new Uint8Array(buffer);
|
||||
for (let i = 0; i < bytes.byteLength; i++) {
|
||||
binary += String.fromCharCode(bytes[i]);
|
||||
}
|
||||
return window.btoa(binary);
|
||||
};
|
||||
// 连接WebSocket
|
||||
connectWebSocket() {
|
||||
return this.getWebSocketUrl().then(url => {
|
||||
let iatWS;
|
||||
if ('WebSocket' in window) {
|
||||
iatWS = new WebSocket(url);
|
||||
} else if ('MozWebSocket' in window) {
|
||||
iatWS = new MozWebSocket(url);
|
||||
} else {
|
||||
alert('浏览器不支持WebSocket!');
|
||||
return false;
|
||||
}
|
||||
this.webSocket = iatWS;
|
||||
this.setStatus('init');
|
||||
iatWS.onopen = e => {
|
||||
this.setStatus('ing');
|
||||
console.log('连接成功')
|
||||
// 重新开始录音
|
||||
setTimeout(() => {
|
||||
this.webSocketSend();
|
||||
}, 500);
|
||||
};
|
||||
iatWS.onmessage = e => {
|
||||
this.webSocketRes(e.data);
|
||||
};
|
||||
iatWS.onerror = e => {
|
||||
this.recorderStop(e);
|
||||
};
|
||||
iatWS.onclose = e => {
|
||||
this.recorderStop(e);
|
||||
};
|
||||
})
|
||||
};
|
||||
// 初始化浏览器录音
|
||||
recorderInit() {
|
||||
// 创建音频环境
|
||||
try {
|
||||
this.audioContext = this.audioContext ? this.audioContext : new (window.AudioContext || window.webkitAudioContext)();
|
||||
this.audioContext.resume();
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口');
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
if (!this.audioContext) {
|
||||
alert('浏览器不支持webAudioApi相关接口');
|
||||
return false;
|
||||
}
|
||||
};
|
||||
// 获取浏览器录音权限成功时回调
|
||||
let getMediaSuccess = _ => {
|
||||
// 创建一个用于通过JavaScript直接处理音频
|
||||
this.scriptProcessor = this.audioContext.createScriptProcessor(0, 1, 1);
|
||||
this.scriptProcessor.onaudioprocess = e => {
|
||||
if (this.status === 'ing') {
|
||||
// 多线程音频数据处理
|
||||
try {
|
||||
this.webWorker.postMessage(e.inputBuffer.getChannelData(0));
|
||||
} catch (error) { }
|
||||
}
|
||||
}
|
||||
// 创建一个新的MediaStreamAudioSourceNode 对象,使来自MediaStream的音频可以被播放和操作
|
||||
this.mediaSource = this.audioContext.createMediaStreamSource(this.streamRef);
|
||||
this.mediaSource.connect(this.scriptProcessor);
|
||||
this.scriptProcessor.connect(this.audioContext.destination);
|
||||
this.connectWebSocket();
|
||||
};
|
||||
// 获取浏览器录音权限失败时回调
|
||||
let getMediaFail = (e) => {
|
||||
alert('对不起:录音权限获取失败!');
|
||||
this.audioContext && this.audioContext.close();
|
||||
this.audioContext = undefined;
|
||||
// 关闭websocket
|
||||
if (this.webSocket && this.webSocket.readyState === 1) {
|
||||
this.webSocket.close();
|
||||
}
|
||||
};
|
||||
navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
|
||||
// 获取浏览器录音权限
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices.getUserMedia({
|
||||
audio: true
|
||||
}).then(stream => {
|
||||
this.streamRef = stream;
|
||||
getMediaSuccess();
|
||||
}).catch(e => {
|
||||
getMediaFail(e);
|
||||
})
|
||||
} else if (navigator.getUserMedia) {
|
||||
navigator.getUserMedia({
|
||||
audio: true
|
||||
}, (stream) => {
|
||||
this.streamRef = stream;
|
||||
getMediaSuccess();
|
||||
}, function (e) {
|
||||
getMediaFail(e);
|
||||
})
|
||||
} else {
|
||||
if (navigator.userAgent.toLowerCase().match(/chrome/) && location.origin.indexOf('https://') < 0) {
|
||||
console.error('获取浏览器录音功能,因安全性问题,需要在localhost 或 127.0.0.1 或 https 下才能获取权限!');
|
||||
} else {
|
||||
alert('对不起:未识别到录音设备!');
|
||||
}
|
||||
this.audioContext && this.audioContext.close();
|
||||
return false;
|
||||
};
|
||||
};
|
||||
// 向webSocket发送数据(音频二进制数据经过Base64处理)
|
||||
webSocketSend() {
|
||||
if (this.webSocket.readyState !== 1) return false;
|
||||
|
||||
// 音频数据
|
||||
const audioData = this.audioData.splice(0, 1280);
|
||||
const params = {
|
||||
common: {
|
||||
app_id: this.appId,
|
||||
},
|
||||
business: {
|
||||
language: this.language, //小语种可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
domain: 'iat',
|
||||
accent: this.accent, //中文方言可在控制台--语音听写(流式)--方言/语种处添加试用
|
||||
vad_eos: 5000,
|
||||
dwa: 'wpgs' //为使该功能生效,需到控制台开通动态修正功能(该功能免费)
|
||||
},
|
||||
data: {
|
||||
status: 0,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(audioData)
|
||||
}
|
||||
};
|
||||
// 发送数据
|
||||
this.webSocket.send(JSON.stringify(params));
|
||||
this.handlerInterval = setInterval(() => {
|
||||
// websocket未连接
|
||||
if (this.webSocket.readyState !== 1) {
|
||||
this.audioData = [];
|
||||
clearInterval(this.handlerInterval);
|
||||
return false;
|
||||
};
|
||||
if (this.audioData.length === 0) {
|
||||
if (this.status === 'end') {
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 2,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: ''
|
||||
}
|
||||
})
|
||||
);
|
||||
this.audioData = [];
|
||||
clearInterval(this.handlerInterval);
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
// 中间帧
|
||||
this.webSocket.send(
|
||||
JSON.stringify({
|
||||
data: {
|
||||
status: 1,
|
||||
format: 'audio/L16;rate=16000',
|
||||
encoding: 'raw',
|
||||
audio: this.toBase64(this.audioData.splice(0, 1280))
|
||||
}
|
||||
})
|
||||
);
|
||||
}, 40);
|
||||
};
|
||||
// 识别结束 webSocket返回数据
|
||||
webSocketRes(resultData) {
|
||||
|
||||
let jsonData = JSON.parse(resultData);
|
||||
console.log(JSON.stringify( jsonData))
|
||||
if (jsonData.data && jsonData.data.result) {
|
||||
let data = jsonData.data.result;
|
||||
let str = '';
|
||||
let ws = data.ws;
|
||||
for (let i = 0; i < ws.length; i++) {
|
||||
str = str + ws[i].cw[0].w;
|
||||
}
|
||||
// 开启wpgs会有此字段(前提:在控制台开通动态修正功能)
|
||||
// 取值为 "apd"时表示该片结果是追加到前面的最终结果;取值为"rpl" 时表示替换前面的部分结果,替换范围为rg字段
|
||||
if (data.pgs) {
|
||||
if (data.pgs === 'apd') {
|
||||
// 将resultTextTemp同步给resultText
|
||||
this.setResultText({
|
||||
resultText: this.resultTextTemp
|
||||
});
|
||||
}
|
||||
// 将结果存储在resultTextTemp中
|
||||
this.setResultText({
|
||||
resultTextTemp: this.resultText + str
|
||||
});
|
||||
} else {
|
||||
this.setResultText({
|
||||
resultText: this.resultText + str
|
||||
});
|
||||
}
|
||||
}
|
||||
if (jsonData.code === 0 && jsonData.data.status === 2) {
|
||||
this.webSocket.close();
|
||||
|
||||
}
|
||||
if (jsonData.code !== 0) {
|
||||
this.webSocket.close();
|
||||
|
||||
}
|
||||
};
|
||||
// 启动录音
|
||||
recorderStart() {
|
||||
|
||||
if (!this.audioContext) {
|
||||
this.recorderInit();
|
||||
} else {
|
||||
this.audioContext.resume();
|
||||
this.connectWebSocket();
|
||||
}
|
||||
};
|
||||
// 停止录音
|
||||
recorderStop() {
|
||||
if (!(/Safari/.test(navigator.userAgent) && !/Chrome/.test(navigator.userAgen))) {
|
||||
// safari下suspend后再次resume录音内容将是空白,设置safari下不做suspend
|
||||
this.audioContext && this.audioContext.suspend();
|
||||
}
|
||||
this.setStatus('end');
|
||||
try {
|
||||
// this.streamRef.getTracks().map(track => track.stop()) || his.streamRef.getAudioTracks()[0].stop();
|
||||
} catch (error) {
|
||||
console.error('暂停失败!');
|
||||
}
|
||||
};
|
||||
// 开始
|
||||
start() {
|
||||
console.log('startstartstartstartstartstart');
|
||||
this.recorderStart();
|
||||
this.setResultText({ resultText: '', resultTextTemp: '' });
|
||||
};
|
||||
// 停止
|
||||
stop() {
|
||||
this.recorderStop();
|
||||
};
|
||||
};
|
||||
}));
|
Binary file not shown.
Before Width: | Height: | Size: 446 B |
Binary file not shown.
Before Width: | Height: | Size: 1.1 KiB |
Loading…
Reference in New Issue