SmartMeeting/speakr/static/js/wsconnecter.js

279 lines
7.1 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* Realtime ASR websocket client.
* Browser audio is sent only to the Speakr backend proxy.
*/
function getBackendAsrWebSocketUrl(mode) {
const protocol = window.location.protocol === "https:" ? "wss:" : "ws:";
const basePath = "/tool/speakr/ws/asr/live";
return `${protocol}//${window.location.host}${basePath}?mode=${encodeURIComponent(mode || "online")}`;
}
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未连接警告节流
var gracefulStopResolver = null;
var gracefulStopTimer = null;
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();
}
if (e === "offline" || (e && e !== "online")) {
modeType = "offline";
} else {
modeType = "online";
}
var Uri = getBackendAsrWebSocketUrl(modeType);
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!");
onClose(e);
};
speechSokt.onmessage = function (e) {
onMessage(e);
};
speechSokt.onerror = function (e) {
onError(e);
};
return 1;
} else {
alert("当前浏览器不支持 WebSocket");
return 0;
}
};
// 定义停止与发送函数
function resolveGracefulStop() {
if (gracefulStopTimer) {
clearTimeout(gracefulStopTimer);
gracefulStopTimer = null;
}
if (gracefulStopResolver) {
gracefulStopResolver();
gracefulStopResolver = null;
}
}
function closeSocketNow() {
if (speechSokt != undefined) {
try {
speechSokt.close();
} catch (e) {
console.warn("Failed to close WebSocket", e);
}
speechSokt = undefined;
}
sendQueue = [];
isSending = false;
}
this.wsStop = function () {
if (speechSokt != undefined) {
console.log("stop ws!");
try {
if (speechSokt.readyState === 1) {
speechSokt.send(JSON.stringify({ type: "stop", is_speaking: false }));
}
} catch (e) {
console.warn("Failed to send WebSocket stop frame", e);
}
}
resolveGracefulStop();
closeSocketNow();
};
this.wsFinish = function (timeoutMs = 3000) {
if (speechSokt == undefined || speechSokt.readyState >= 2) {
resolveGracefulStop();
return Promise.resolve();
}
sendQueue = [];
isSending = false;
return new Promise((resolve) => {
gracefulStopResolver = resolve;
gracefulStopTimer = setTimeout(() => {
// Give the server ownership of the close. Timeout only unblocks UI.
resolveGracefulStop();
}, timeoutMs);
try {
if (speechSokt.readyState === 1) {
speechSokt.send(JSON.stringify({ type: "stop", is_speaking: false }));
}
} catch (e) {
console.warn("Failed to send WebSocket finish frame", e);
resolveGracefulStop();
}
});
};
// 处理发送队列
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
};
};
// SOCEKT连接中的消息与状态响应
function onOpen(e) {
console.log(e, "e");
console.log("WebSocket connected to backend ASR proxy");
stateHandle(0);
if (sendQueue.length > 0) {
console.log(`Processing ${sendQueue.length} queued audio chunks`);
processQueue();
}
}
function onClose(e) {
resolveGracefulStop();
speechSokt = undefined;
stateHandle(1);
}
function onMessage(e) {
msgHandle(e);
try {
const payload = JSON.parse(e.data);
if (payload && payload.type === "state" && payload.state === "closed") {
resolveGracefulStop();
}
} catch (_) {}
}
function onError(e) {
// info_div.innerHTML="连接"+e;
console.log(e);
stateHandle(2);
}
}