316 lines
8.5 KiB
JavaScript
316 lines
8.5 KiB
JavaScript
/**
|
||
* Copyright FunASR (https://github.com/alibaba-damo-academy/FunASR). All Rights
|
||
* Reserved. MIT License (https://opensource.org/licenses/MIT)
|
||
*/
|
||
/* 2021-2023 by zhaoming,mali aihealthx.com */
|
||
let isfilemode = false;
|
||
let configwords = "";
|
||
let wssBaseUrl={};
|
||
|
||
|
||
//------------------- WebSocket 配置初始化 start------------------
|
||
async function init() {
|
||
// 在这里请求接口
|
||
wssBaseUrl = await getBaseUrlFromApi();
|
||
|
||
console.log(wssBaseUrl, "获取baseUrl");
|
||
}
|
||
|
||
// 示例:请求接口获取 baseUrl
|
||
async function getBaseUrlFromApi() {
|
||
try {
|
||
const response = await fetch(
|
||
`getUserConfig`,
|
||
{
|
||
method: "GET",
|
||
}
|
||
);
|
||
|
||
if (!response.ok) {
|
||
throw new Error("请求失败");
|
||
}
|
||
|
||
const data = await response.json();
|
||
configwords = data
|
||
return configwords;
|
||
} catch (error) {
|
||
console.error("获取 configwords 出错:", error);
|
||
}
|
||
}
|
||
|
||
// 初始化程序入口
|
||
init();
|
||
console.log("进入ws");
|
||
|
||
//--------------------WebSocket 配置初始化 end------------------
|
||
|
||
//------------------- FunASR WebSocket 连接器 start------------------
|
||
function WebSocketConnectMethod(config) {
|
||
//定义socket连接方法类
|
||
|
||
var speechSokt;
|
||
var connKeeperID;
|
||
|
||
var msgHandle = config.msgHandle;
|
||
var stateHandle = config.stateHandle;
|
||
let modeType = "offline";
|
||
|
||
// 发送队列和流量控制
|
||
var sendQueue = [];
|
||
var isSending = false;
|
||
var maxQueueSize = 1000; // 最大队列长度,防止内存溢出
|
||
var queueWarningThreshold = 500; // 队列警告阈值
|
||
var lastDisconnWarnTime = 0; // WebSocket未连接警告节流
|
||
function trimQueueIfNeeded() {
|
||
if (sendQueue.length <= maxQueueSize) {
|
||
return false;
|
||
}
|
||
|
||
const dropCount = sendQueue.length - maxQueueSize;
|
||
console.warn(
|
||
`⚠️ 发送队列超过最大限制 (${maxQueueSize}),丢弃最旧的 ${dropCount} 个数据包`
|
||
);
|
||
sendQueue = sendQueue.slice(-maxQueueSize);
|
||
return true;
|
||
}
|
||
|
||
function enqueueData(data) {
|
||
sendQueue.push(data);
|
||
trimQueueIfNeeded();
|
||
|
||
if (sendQueue.length > queueWarningThreshold) {
|
||
console.warn(`⚠️ WebSocket发送队列堆积: ${sendQueue.length} 个数据包待发送`);
|
||
}
|
||
}
|
||
|
||
this.wsStart = function (e) {
|
||
if (speechSokt) {
|
||
speechSokt.close();
|
||
}
|
||
//------------------- FunASR 连接模式选择 start------------------
|
||
// app.js 中 rec 和 rec2 可能连接同一个 /websocket_offline 地址。
|
||
// online/offline 的区别不靠 URL,而靠 onOpen() 握手 JSON 中的 mode 字段:
|
||
// wsStart() 不传参数 => mode:"online"; wsStart(url) 传参 => mode:"offline"。
|
||
var Uri = e ? e : `wss://${wssBaseUrl.FUNASR_WEBSOCKET_IP}/websocket_offline`;
|
||
if (e) {
|
||
modeType = "offline";
|
||
} else {
|
||
modeType = "online";
|
||
}
|
||
//--------------------FunASR 连接模式选择 end------------------
|
||
if (Uri.match(/wss:\S*|ws:\S*/)) {
|
||
console.log("Uri" + Uri);
|
||
} else {
|
||
alert("请检查wss地址正确性");
|
||
return 0;
|
||
}
|
||
|
||
if ("WebSocket" in window) {
|
||
speechSokt = new WebSocket(Uri); // 定义socket连接对象
|
||
speechSokt.onopen = function (e) {
|
||
onOpen(e);
|
||
}; // 定义响应函数
|
||
speechSokt.onclose = function (e) {
|
||
console.log("onclose ws!");
|
||
//speechSokt.close();
|
||
onClose(e);
|
||
};
|
||
speechSokt.onmessage = function (e) {
|
||
onMessage(e);
|
||
};
|
||
speechSokt.onerror = function (e) {
|
||
onError(e);
|
||
};
|
||
return 1;
|
||
} else {
|
||
alert("当前浏览器不支持 WebSocket");
|
||
return 0;
|
||
}
|
||
};
|
||
|
||
// 定义停止与发送函数
|
||
this.wsStop = function () {
|
||
if (speechSokt != undefined) {
|
||
console.log("stop ws!");
|
||
speechSokt.close();
|
||
}
|
||
// 清空发送队列
|
||
sendQueue = [];
|
||
isSending = false;
|
||
};
|
||
|
||
// 处理发送队列
|
||
function processQueue() {
|
||
if (
|
||
isSending ||
|
||
sendQueue.length === 0 ||
|
||
!speechSokt ||
|
||
speechSokt.readyState !== 1
|
||
) {
|
||
return;
|
||
}
|
||
|
||
isSending = true;
|
||
const data = sendQueue.shift();
|
||
|
||
try {
|
||
speechSokt.send(data);
|
||
isSending = false;
|
||
|
||
// 队列警告
|
||
if (sendQueue.length > queueWarningThreshold) {
|
||
console.warn(`⚠️ WebSocket发送队列堆积: ${sendQueue.length} 个数据包待发送`);
|
||
}
|
||
|
||
// 继续处理队列
|
||
if (sendQueue.length > 0) {
|
||
// 使用 setTimeout 避免阻塞
|
||
setTimeout(() => processQueue(), 0);
|
||
}
|
||
} catch (e) {
|
||
console.error("❌ WebSocket发送失败,数据已放回队列:", e);
|
||
sendQueue.unshift(data); // 放回队列头部
|
||
isSending = false;
|
||
trimQueueIfNeeded();
|
||
}
|
||
}
|
||
|
||
this.wsSend = function (oneData) {
|
||
if (speechSokt == undefined) {
|
||
// WebSocket未初始化,缓存数据
|
||
enqueueData(oneData);
|
||
return true;
|
||
}
|
||
|
||
if (speechSokt.readyState === 1) {
|
||
// WebSocket已打开
|
||
if (sendQueue.length > 0) {
|
||
// 队列中有数据,先加入队列
|
||
enqueueData(oneData);
|
||
processQueue();
|
||
return true;
|
||
} else {
|
||
// 队列为空,尝试直接发送
|
||
try {
|
||
speechSokt.send(oneData);
|
||
return true;
|
||
} catch (e) {
|
||
console.error("❌ WebSocket直接发送失败,加入队列:", e);
|
||
enqueueData(oneData);
|
||
processQueue();
|
||
return true;
|
||
}
|
||
}
|
||
} else if (speechSokt.readyState === 0) {
|
||
// WebSocket正在连接,缓存数据
|
||
enqueueData(oneData);
|
||
return true;
|
||
} else {
|
||
// WebSocket已关闭或关闭中,丢弃数据并警告(节流 5 秒)
|
||
var now = Date.now();
|
||
if (now - lastDisconnWarnTime > 5000) {
|
||
console.warn("⚠️ WebSocket未连接,数据无法发送 (state:", speechSokt.readyState, ")");
|
||
lastDisconnWarnTime = now;
|
||
}
|
||
return false;
|
||
}
|
||
};
|
||
|
||
// 获取队列状态(用于调试)
|
||
this.getQueueStatus = function () {
|
||
return {
|
||
length: sendQueue.length,
|
||
isSending: isSending,
|
||
readyState: speechSokt ? speechSokt.readyState : -1
|
||
};
|
||
};
|
||
|
||
async function getBaseHot(params) {
|
||
try {
|
||
const response = await fetch(`/tool/speakr/api/hotwords`, {
|
||
method: "GET",
|
||
});
|
||
if (!response.ok) {
|
||
// 请求失败时返回空
|
||
console.warn("getBaseHot 请求失败,使用默认 hotwords");
|
||
return "{}";
|
||
}
|
||
|
||
const data = await response.json();
|
||
|
||
const resultObj = data.hotwords.reduce((obj, item) => {
|
||
obj[item.keyword] = item.weight;
|
||
return obj;
|
||
}, {});
|
||
|
||
return JSON.stringify(resultObj);
|
||
|
||
} catch (err) {
|
||
// 发生异常时返回空
|
||
console.error("getBaseHot 调用出错:", err);
|
||
return "";
|
||
}
|
||
}
|
||
|
||
// SOCEKT连接中的消息与状态响应
|
||
async function onOpen(e) {
|
||
console.log(e, "e");
|
||
|
||
// 发送json
|
||
var chunk_size = new Array(10, 10, 10);
|
||
var request = {
|
||
chunk_size: chunk_size,
|
||
wav_name: "h5",
|
||
is_speaking: true,
|
||
chunk_interval: 10,
|
||
itn: true, // 是否使用ITN
|
||
// "mode":"2pass",// 模型模式
|
||
// 对应 app.js 的 rec/rec2 双链路:同地址下通过 mode 区分在线预览和离线正式结果。
|
||
mode: modeType,
|
||
hotwords: await getBaseHot(),
|
||
};
|
||
console.log(request, "request");
|
||
|
||
if (isfilemode) {
|
||
request.wav_format = file_ext;
|
||
if (file_ext == "wav") {
|
||
request.wav_format = "PCM";
|
||
request.audio_fs = file_sample_rate;
|
||
}
|
||
}
|
||
|
||
// var hotwords=getBaseHot();
|
||
// console.log(hotwords);
|
||
|
||
// if(hotwords!=null )
|
||
// {
|
||
// request.hotwords=hotwords;
|
||
// }
|
||
// console.log(JSON.stringify(request),'热词');
|
||
speechSokt.send(JSON.stringify(request));
|
||
console.log("连接成功");
|
||
stateHandle(0);
|
||
|
||
// 连接成功后,处理队列中积压的数据
|
||
if (sendQueue.length > 0) {
|
||
console.log(`📤 处理连接前积压的 ${sendQueue.length} 个数据包`);
|
||
processQueue();
|
||
}
|
||
}
|
||
|
||
function onClose(e) {
|
||
stateHandle(1);
|
||
}
|
||
|
||
function onMessage(e) {
|
||
msgHandle(e);
|
||
}
|
||
|
||
function onError(e) {
|
||
// info_div.innerHTML="连接"+e;
|
||
console.log(e);
|
||
stateHandle(2);
|
||
}
|
||
//--------------------FunASR WebSocket 连接器 end------------------
|
||
}
|