封装工具代码
/*
我使用的是:微信同声传译插件
实现功能:
1.语音输入
2.文本翻译
3.语音合成
*/
import { ref } from 'vue'
const plugin = requirePlugin('WechatSI')
// ------------------------------ 语音输入功能 开始 ------------------------------
// 管理器
const manager = plugin.getRecordRecognitionManager()
// 识别结果
const recognizedText = ref('')
// 是否正在识别
const isRecognizing = ref(false)
// 开始识别
/*
options: 识别配置
duration Number 否 60000 指定录音的时长,单位ms,最大为60000。如果传入了合法的 duration ,在到达指定的 duration 后会自动停止录音
lang String 否 zh_CN 识别的语言,目前支持 zh_CN(中国大陆) en_US(英语) sichuanhua(四川话) yue(粤语)
*/
const startRecord = (options) => {
if (isRecognizing.value) {
uni.showToast({
title: '正在识别中,请先停止识别',
icon: 'none',
})
return
}
isRecognizing.value = true
manager.start({
duration: options?.duration || 60000,
lang: options?.lang || 'zh_CN',
})
}
manager.onStart = function (res) {
console.log('成功开始录音识别', res)
}
// 识别结束
const stopRecord = () => {
isRecognizing.value = false
manager.stop()
}
/*
tempFilePath String 录音临时文件地址
duration Number 录音总时长,单位: ms
fileSize Number 文件大小,单位: B
result String 最终识别结果
*/
manager.onStop = function (res) {
console.log('识别结束', res)
recognizedText.value = res.result
isRecognizing.value = false
}
// 识别错误
/*
retcode Int 错误码
msg String 错误信息
*/
manager.onError = function (res) {
console.error('识别错误', res)
let msg = ''
switch (res.retcode) {
case -30001:
msg = '录音接口出错'
break
case -30002:
msg = '录音暂停接口被调用,录音终止,识别终止'
break
case -30003:
msg = '录音帧数据未产生或者发送失败导致的数据传输失败'
break
case -30004:
msg = '因网络或者其他非正常状态导致的未查询识别结果'
break
case -30005:
msg = '语音识别服务内部错误'
break
case -30006:
msg = '语音识别服务未在限定时间内识别完成'
break
case -30007:
msg = 'start启动参数错误'
break
case -30008:
msg = '查询请求时网络失败'
break
case -30009:
msg = '创建鉴权内部失败'
break
case -30010:
msg = '发送鉴权时网络失败'
break
case -30011:
msg = '试图在识别正在进行中是再次调用start,返回错误,正在进行的识别任务正常进行'
break
case -30012:
msg = '当前无识别任务进行时调用stop错误'
break
case -30013:
msg = '其他未知错误'
break
case -40001:
msg = '达到接口调用频率限制'
break
default:
msg = res.msg || '其他未知错误'
break
}
isRecognizing.value = false
uni.showModal({
title: '提示',
content: msg,
showCancel: false,
})
}
// ------------------------------ 语音输入功能 结束 ------------------------------
const audioContext = uni.createInnerAudioContext()
// ------------------------------ 文本翻译功能 开始 ------------------------------
// 文本翻译结果
const translatedText = ref('')
// 是否正在翻译
const isTranslating = ref(false)
// 文本翻译后合成的语音地址
const translatedVoiceUrl = ref('')
// 开始翻译
/*
参数说明:
lfrom String 是 文本语言 zh_CN(中国大陆) en_US(英语) yue(粤语)
lto String 是 目标语言 zh_CN(中国大陆) en_US(英语)yue(粤语)
content String 是 需要被翻译的文本内容,后台限制200字节大小
tts Boolean 否 是否对翻译结果进行语音合成,默认为false,不进行语音合成
*/
const translateText = (options) => {
if (isTranslating.value) {
uni.showToast({
title: '正在翻译中,请稍后再试',
icon: 'none',
})
return
}
if (!options.lfrom) {
uni.showToast({
title: '请输入文本语言',
icon: 'none',
})
return
}
if (!options.lto) {
uni.showToast({
title: '请输入目标语言',
icon: 'none',
})
return
}
if (options.lfrom === options.lto) {
uni.showToast({
title: '文本语言和目标语言不能相同',
icon: 'none',
})
return
}
if (!options.content) {
uni.showToast({
title: '请输入文本内容',
icon: 'none',
})
return
}
isTranslating.value = true
plugin.translate({
lfrom: options.lfrom,
lto: options.lto,
content: options.content,
tts: options.tts || false,
success: (res) => {
/*
retcode Int 状态码
0 翻译合成成功
-10006 翻译成功,合成内部错误
-10007 翻译成功,传入了不支持的语音合成语言
-10008 翻译成功,语音合成达到频率限制
origin String 原始文本
result String 翻译结果
filename String 语音合成返回的语音地址,仅支持合成中文语音
expired_time Int 语音合成链接超时时间戳 如1525930552超时后无法播放,可使用时间为3小时
*/
console.log('翻译结果', res)
translatedText.value = res.result
translatedVoiceUrl.value = res.filename
isTranslating.value = false
},
fail: (res) => {
/*
retcode Int 错误码
-10001 语言检查错误
-10002 输入的待翻译内容格式不正确
-10003 传入过长的待翻译文本内容
-10004 翻译内部逻辑错误
-10005 请求发送失败,请检查网络
-40001 接口调用频率达到限制,请联系插件开发者
msg String 错误信息
*/
console.error('翻译失败', res)
let msg = ''
switch (res.retcode) {
case -10001:
msg = '语言检查错误'
break
case -10002:
msg = '输入的待翻译内容格式不正确'
break
case -10003:
msg = '传入过长的待翻译文本内容'
break
case -10004:
msg = '翻译内部逻辑错误'
break
case -10005:
msg = '请求发送失败,请检查网络'
break
case -40001:
msg = '接口调用频率达到限制,请联系插件开发者'
break
default:
msg = res.msg || '其他未知错误'
break
}
isTranslating.value = false
uni.showModal({
title: '提示',
content: msg,
showCancel: false,
})
},
})
}
const playTranslatedVoice = () => {
if (isTranslating.value) {
uni.showToast({
title: '正在翻译中,请稍后再试',
icon: 'none',
})
return
}
if (!translatedVoiceUrl.value) {
uni.showToast({
title: '没有翻译结果音频',
icon: 'none',
})
return
}
audioContext.stop()
audioContext.src = translatedVoiceUrl.value
audioContext.play()
}
// ------------------------------ 文本翻译功能 结束 ------------------------------
// ------------------------------ 语音合成功能 开始 ------------------------------
const voiceUrl = ref('')
// 是否正在语音合成
const isTextToSpeech = ref(false)
/*
参数说明:
lang String 是 文本语言 zh_CN(中国大陆)en_US(英文)
content String 是 需要被合成的文本内容,后台限制长度在50个字符内
success Function 调用成功时触发的callback
fail Function 调用失败时触发的callback
*/
const textToSpeech = (options) => {
if (isTextToSpeech.value) {
uni.showToast({
title: '正在语音合成中,请稍后再试',
icon: 'none',
})
return
}
if (!options.lang) {
uni.showToast({
title: '请输入文本语言',
icon: 'none',
})
return
}
if (!options.content) {
uni.showToast({
title: '请输入文本内容',
icon: 'none',
})
return
}
isTextToSpeech.value = true
plugin.textToSpeech({
lang: options.lang,
tts: true,
content: options.content,
success: function (res) {
/*
retcode Int retcode == 0 时请求成功
origin String 原始文本
filename String 语音合成返回的语音地址,可自行下载使用
expired_time Int 语音合成链接超时时间戳 如1525930552,超时后无法播放,可使用时间为3小时
*/
console.log('语音合成结果', res)
voiceUrl.value = res.filename
isTextToSpeech.value = false
},
fail: function (res) {
/*
retcode Int 错误码
-20001 语音合成语言格式出错
-20002 输入的待合成格式不正确
-20003 语音合成内部错误
-20005 网络错误
-40001 接口调用频率达到限制,请联系插件开发者
msg String 错误信息
*/
console.error('语音合成失败', res)
let msg = ''
switch (res.retcode) {
case -20001:
msg = '语音合成语言格式出错'
break
case -20002:
msg = '输入的待合成格式不正确'
break
case -20003:
msg = '语音合成内部错误'
break
case -20005:
msg = '网络错误'
break
case -40001:
msg = '接口调用频率达到限制,请联系插件开发者'
break
default:
msg = res.msg || '其他未知错误'
break
}
isTextToSpeech.value = false
uni.showModal({
title: '提示',
content: msg,
showCancel: false,
})
},
})
}
const playVoice = () => {
if (isTextToSpeech.value) {
uni.showToast({
title: '正在语音合成中,请稍后再试',
icon: 'none',
})
return
}
if (!voiceUrl.value) {
uni.showToast({
title: '没有语音合成结果音频',
icon: 'none',
})
return
}
audioContext.stop()
audioContext.src = voiceUrl.value
audioContext.play()
}
// ------------------------------ 语音合成功能 结束 ------------------------------
export default {
recognizedText,
translatedText,
translatedVoiceUrl,
voiceUrl,
startRecord,
stopRecord,
translateText,
playTranslatedVoice,
textToSpeech,
playVoice,
}
示例页面
<template>
<div class="page-box">
<div class="box">
<div class="title">语音识别结果 :</div>
<div>{{ face2FaceTranslator.recognizedText || '暂无结果' }}</div>
</div>
<div class="box box-space">
<div class="btn" @click="startRecord">开始录音</div>
<div class="btn" @click="stopRecord">结束录音</div>
</div>
<div class="line-box">
<div class="line"></div>
<div class="text">分割线</div>
<div class="line"></div>
</div>
<div class="box">
<div class="title">文本语言 : </div>
<div>
<radio-group>
<div class="radio-box">
<div class="radio-item" v-for="item in translatedLangList" :key="item.id">
<div class="name">{{ item.name }}</div>
<radio
:value="item.id"
:checked="item.id === translatedForm"
@click="translatedForm = item.id"
/>
</div>
</div>
</radio-group>
</div>
</div>
<div class="box">
<div class="title">目标语言 : </div>
<div>
<radio-group>
<div class="radio-box">
<div class="radio-item" v-for="item in translatedLangList" :key="item.id">
<div class="name">{{ item.name }}</div>
<radio
:value="item.id"
:checked="item.id === translatedTo"
@click="translatedTo = item.id"
/>
</div>
</div>
</radio-group>
</div>
</div>
<div class="box">
<textarea v-model="translatedContent" placeholder="请输入翻译文本" />
</div>
<div class="box">
<div class="title">翻译结果 :</div>
<div>{{ face2FaceTranslator.translatedText || '暂无结果' }}</div>
</div>
<div class="box box-space">
<div class="btn" @click="translateText">翻译</div>
<div class="btn" @click="playTranslatedVoice">播放翻译结果</div>
</div>
<div class="line-box">
<div class="line"></div>
<div class="text">分割线</div>
<div class="line"></div>
</div>
<div class="box">
<div class="title">文本语言 : </div>
<div>
<radio-group>
<div class="radio-box">
<div class="radio-item" v-for="item in speechLangList" :key="item.id">
<div class="name">{{ item.name }}</div>
<radio
:value="item.id"
:checked="item.id === speechLang"
@click="speechLang = item.id"
/>
</div>
</div>
</radio-group>
</div>
</div>
<div class="box">
<textarea v-model="speechContent" placeholder="请输入语音合成文本" />
</div>
<div class="box box-space">
<div class="btn" @click="textToSpeech">语音合成</div>
<div class="btn" @click="playVoice">播放语音合成结果</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, computed, watchEffect, getCurrentInstance } from 'vue';
import { onLoad } from '@dcloudio/uni-app';
const { proxy } = getCurrentInstance();
onMounted(() => {});
onLoad((res) => {});
import face2FaceTranslator from '@/utils/Face2FaceTranslator.js';
function startRecord() {
face2FaceTranslator.startRecord();
}
function stopRecord() {
face2FaceTranslator.stopRecord();
}
const translatedLangList = ref([
{
id: 'zh_CN',
name: '中文',
},
{
id: 'en_US',
name: '英文',
},
{
id: 'yue',
name: '粤语',
},
]);
const translatedForm = ref('');
const translatedTo = ref('');
const translatedContent = ref('');
function translateText() {
face2FaceTranslator.translateText({
lfrom: translatedForm.value,
lto: translatedTo.value,
content: translatedContent.value,
tts: true,
});
}
function playTranslatedVoice() {
face2FaceTranslator.playTranslatedVoice();
}
const speechLangList = ref([
{
id: 'zh_CN',
name: '中文',
},
{
id: 'en_US',
name: '英文',
},
]);
const speechLang = ref('');
const speechContent = ref('');
function textToSpeech() {
face2FaceTranslator.textToSpeech({
lang: speechLang.value,
content: speechContent.value,
});
}
function playVoice() {
face2FaceTranslator.playVoice();
}
</script>
<style>
page {
background: #f5f5f5;
}
</style>
<style lang="scss" scoped>
.page-box {
padding: 30rpx;
}
.box {
margin-bottom: 20rpx;
background: #ffffff;
box-shadow: 0rpx 8rpx 20rpx 0rpx rgba(9, 198, 116, 0.1);
border-radius: 24rpx 24rpx 24rpx 24rpx;
padding: 30rpx;
font-weight: normal;
font-size: 28rpx;
color: #000000;
}
.title {
font-weight: normal;
font-size: 28rpx;
color: #000000;
margin-bottom: 20rpx;
}
.radio-box {
display: flex;
align-items: center;
.radio-item {
display: flex;
align-items: center;
margin-right: 20rpx;
.name {
font-weight: normal;
font-size: 28rpx;
color: #000000;
margin-right: 20rpx;
}
}
}
textarea {
width: auto;
border: 2rpx solid #ccc;
border-radius: 24rpx;
padding: 20rpx;
background: #eee;
height: 200rpx;
}
.box-space {
display: flex;
justify-content: space-between;
align-items: center;
}
.btn {
width: 45%;
display: flex;
justify-content: center;
align-items: center;
padding: 20rpx;
border-radius: 50rpx;
background: #0374f8;
font-weight: normal;
font-size: 28rpx;
color: #fff;
}
.line-box {
margin: 40rpx;
display: flex;
justify-content: space-between;
align-items: center;
text {
font-weight: normal;
font-size: 28rpx;
color: #000000;
margin: 0 20rpx;
}
.line {
flex: 1;
height: 2rpx;
background: #ccc;
}
}
</style>