前端 vue3 对接科大讯飞的语音在线合成API
主要的功能就是将文本转为语音,可以播放。
看了看官方提供的demo,嗯....没看懂。最后还是去网上找的。
网上提供的案例,很多都是有局限性的,我找的那个他只能读取第一段数据,剩下的不读取。
科大讯飞的接口,返回的是一个数组,因为需要合成的文本多,所以将数据切割成多份,然后返回的。
例子:
封装了个方法,直接调用方法就可以了。
import CryptoJS from 'crypto-js';
import { Base64 } from 'js-base64';
import { message } from 'ant-design-vue';
let APPID = '';
let API_SECRET = '';
let API_KEY = '';
// 正确的URL
function getWebSocketUrl(apiKey, apiSecret) {
let url = 'wss://tts-api.xfyun.cn/v2/tts';
const host = 'tts-api.xfyun.cn';
const date = new Date().toGMTString();
const algorithm = 'hmac-sha256';
const headers = 'host date request-line';
const signatureOrigin = `host: ${host}\ndate: ${date}\nGET /v2/tts HTTP/1.1`;
const signatureSha = CryptoJS.HmacSHA256(signatureOrigin, apiSecret);
const signature = CryptoJS.enc.Base64.stringify(signatureSha);
const authorizationOrigin = `api_key="${apiKey}", algorithm="${algorithm}", headers="${headers}", signature="${signature}"`;
const authorization = btoa(authorizationOrigin);
url = `${url}?authorization=${authorization}&date=${date}&host=${host}`;
return url;
}
// 文本编码
function encodeText(text, encoding) {
switch (encoding) {
case 'utf16le': {
const buf = new ArrayBuffer(text.length * 4);
const bufView = new Uint16Array(buf);
// eslint-disable-next-line no-plusplus
for (let i = 0, strlen = text.length; i < strlen; i++) {
bufView[i] = text.charCodeAt(i);
}
return buf;
}
case 'buffer2Base64': {
let binary = '';
const bytes = new Uint8Array(text);
const len = bytes.byteLength;
// eslint-disable-next-line no-plusplus
for (let i = 0; i < len; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary);
}
case 'base64&utf16le': {
return this.encodeText(this.encodeText(text, 'utf16le'), 'buffer2Base64');
}
default: {
return Base64.encode(text);
}
}
}
// eslint-disable-next-line no-shadow
function TextToSpeechConfig(APPID, vcn, speed, volume, pitch, tte, text) {
// 私有方法:生成参数对象
function generateParams() {
return {
common: {
app_id: APPID,
},
business: {
aue: 'lame',
auf: 'audio/L16;rate=16000',
sfl: 1,
vcn,
speed,
volume,
pitch,
bgs: 1,
tte,
},
data: {
status: 2,
text: encodeText(text, tte === 'unicode' ? 'base64&utf16le' : ''), // 假设 encodeText 是一个已定义的函数
},
};
}
// 公共方法,暴露给外部调用以获取参数对象
return generateParams();
}
export default class TTSWSS {
static _instance; // 使用下划线表示这是一个内部使用的属性
text = '';
vcn = '';
speed = '';
volume = '';
pitch = '';
tte = 'UTF8';
ttsWS = null;
static getInstance(text, vcn, speed, volume, pitch) { // 单例模式
// if (!TTSWSS._instance) {
// TTSWSS._instance = new TTSWSS(text, vcn, speed, volume, pitch);
// }
TTSWSS._instance = new TTSWSS(text, vcn, speed, volume, pitch);
return TTSWSS._instance;
}
constructor(text, vcn, speed, volume, pitch) {
this.text = text;
this.vcn = vcn;
this.speed = speed;
this.volume = volume;
this.pitch = pitch;
const url = getWebSocketUrl(API_KEY, API_SECRET);
if ('WebSocket' in window) { // 构造函数时就创建websocket对象
this.ttsWS = new WebSocket(url);
} else if ('MozWebSocket' in window) {
this.ttsWS = new WebSocket(url);
} else {
// alert('浏览器不支持WebSocket');
message.error('浏览器不支持WebSocket');
}
}
setText(text) {
this.text = text;
}
setTextVCN(vcn) {
this.vcn = vcn;
}
setSpeed(speed) {
this.speed = speed;
}
setVolume(volume) {
this.volume = volume;
}
// setTte(istte=false){
// this.tte = istte==true ? "unicode" : "UTF8"
// }
connectWebSocket() {
this.ttsWS.onopen = () => {
// console.log(TextToSpeechConfig(APPID, this.vcn, this.speed, this.volume, this.pitch, this.tte, this.text), '请求参数');
this.ttsWS.send(JSON.stringify(TextToSpeechConfig(APPID, this.vcn, this.speed, this.volume, this.pitch, this.tte, this.text)));
};
this.ttsWS.onerror = () => {
// console.error(e);
};
this.ttsWS.onclose = () => {
// console.log(e);
};
}
disconnectWebSocket() {
TTSWSS._instance = null;
this.ttsWS.close(); // 关闭 WebSocket 连接
this.ttsWS = null; // 清空 WebSocket 对象
// console.log('WebSocket disconnected');
}
send_newMessage = text => {
const params = {
common: {
app_id: APPID,
},
business: {
aue: 'lame',
sfl: 1,
auf: 'audio/L16;rate=16000',
vcn: this.vcn,
speed: this.speed,
volume: this.volume,
pitch: this.pitch,
bgs: 1,
tte: 'UTF8',
},
data: {
status: 2,
text: encodeText(text, this.tte === 'unicode' ? 'base64&utf16le' : ''),
},
};
this.ttsWS.send(JSON.stringify(params));
};
getMessage() {
const that = this.ttsWS;
const messages = []; // 用于存储所有消息
return new Promise((resolve, reject) => {
that.onmessage = e => {
const jsonData = JSON.parse(e.data);
// 合成失败
if (jsonData.code !== 0) {
// eslint-disable-next-line prefer-promise-reject-errors
reject({ message: '失败', data: jsonData });
return; // 退出当前处理
}
// 存储成功的消息
messages.push({
message: '成功',
type: 'base64',
data: jsonData.data.audio,
isLastData: jsonData.data.status === 2,
});
// 如果接收到最后一条数据,解析所有消息并关闭连接
if (jsonData.data.status === 2) {
that.close();
resolve(messages); // 返回所有消息
}
};
});
}
TTS_close_reset() {
this.ttsWS?.close();
// audioPlayer.reset();
}
static resetInstance() {
TTSWSS._instance = null; // 清空实例
// console.log('TTSWSS instance has been reset.');
}
}
export function setConfig(params) {
APPID = params?.APPID;
API_SECRET = params?.APISecret;
API_KEY = params?.APIKey;
}
使用:
import TTWss from '@/utils/voice/index.js';
const audio_url = ref('');
const ttsinstance = ref(null); // 初始化为 null
const voiceLoading = ref(false); // 加载音频中
function playVoice() {
voiceLoading.value = true;
ttsinstance?.value?.disconnectWebSocket();
ttsinstance.value = null;
audio_url.value = null; // 清空音频 URL
const { text } = props; // 这里是要转成语音的文字,我这个是写在组件里面的用props接收的,所以要这样写,到时候替换成自己要合成的文字就行
// 创建 TTS 实例
ttsinstance.value = TTWss.getInstance(text, 'xiaoyan', 50, 50, 50);
// 连接 WebSocket
ttsinstance.value.connectWebSocket();
// 获取消息
ttsinstance.value.getMessage().then(result => {
// 这里需要特殊处理,因为返回的数据是数组,所以要先将数组中的数据拿出来,放在每项里面的data中,然将不要先拼接,而是要先解码,然后将解码后的数据在拼接起来,这样就是一整段完整的录音文件了。
const allData = result.map(it => atob(it.data));
const binaryString = allData.join('');
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
const blob = new Blob([bytes], { type: 'audio/mp3' }); // 根据音频格式修改MIME类型
const url = URL.createObjectURL(blob);
audio_url.value = url; // 将生成的 URL 赋值给 audio_url
// 这里展开后可以直接下载
// const aTag = document.createElement('a');
// aTag.href = url;
// aTag.download = 'audio_file_name.mp3'; // 设置文件名
// aTag.style.display = 'none';
// document.body.appendChild(aTag);
// aTag.click();
// document.body.removeChild(aTag);
// 播放音频
playItem(url);
voiceLoading.value = false;
}).catch(err => {
// console.log('失败', err);
message.error(err);
voiceLoading.value = false;
});
}
let currentAudio = null;
function playItem(url) {
// 如果当前有音频在播放,则停止它
if (currentAudio) {
currentAudio.pause();
currentAudio.currentTime = 0; // 可选:重置播放时间
}
currentAudio = new Audio(url);
currentAudio.play().then(() => {
// console.log('音频播放开始');
}).catch(error => {
// console.error('音频播放失败', error);
message.error(error);
});
// 释放对象URL(可选)
currentAudio.addEventListener('ended', () => {
URL.revokeObjectURL(url);
currentAudio = null; // 音频结束后清空实例
});
}
HTML:
<img
:src="PlayVoice"
alt=""
class="icon-img"
@click.stop="playVoice"
/>
原文地址:https://blog.csdn.net/motoudi/article/details/142653230
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!