update:更新比赛服逻辑
This commit is contained in:
+70
-15
@@ -19,6 +19,10 @@ let audioAckListenerReady = false;
|
||||
// 后端要求非心跳消息必须等前端语音播报完成后再 ACK。
|
||||
const pendingAcks = [];
|
||||
|
||||
// 当前仍是“只连接、只解码、只打印”阶段,还没有真正接管比赛语音播报。
|
||||
// 为了先验证协议闭环,这里临时把“打印完成”当作“播报完成”并立即 ACK。
|
||||
const AUTO_ACK_WITHOUT_AUDIO = true;
|
||||
|
||||
// 这些比赛消息需要在 audioEnded 后补 ACK;心跳 ACK 单独即时处理。
|
||||
const ACK_REQUIRED_TYPES = new Set([
|
||||
ServerMessageType.SERVER_MSG_MATCH_READY,
|
||||
@@ -36,6 +40,11 @@ function pickField(source, camelKey, snakeKey) {
|
||||
return source?.[camelKey] ?? source?.[snakeKey];
|
||||
}
|
||||
|
||||
function normalizeId(value) {
|
||||
if (value === undefined || value === null || value === "") return "";
|
||||
return String(value);
|
||||
}
|
||||
|
||||
// 追加 token 查询参数;如果后端地址已经带 token,则保持原样。
|
||||
function appendQuery(url, key, value) {
|
||||
if (!value || new RegExp(`[?&]${key}=`).test(url)) return url;
|
||||
@@ -87,14 +96,11 @@ function sendHeartbeatAck() {
|
||||
|
||||
function sendAck({ matchId, sequence }) {
|
||||
// sequence 由后端处理,前端只原样带回,不做重排和断线补发。
|
||||
if (!sequence) return;
|
||||
if (sequence === undefined || sequence === null || sequence === "") return;
|
||||
sendBuffer(createAckMessage({ matchId, sequence }), `ack ${sequence}`);
|
||||
}
|
||||
|
||||
function flushPendingAcks() {
|
||||
// 每次语音播报完成,只确认一条已播放完成的服务端消息。
|
||||
if (!pendingAcks.length) return;
|
||||
const task = pendingAcks.shift();
|
||||
function completeAckTask(task) {
|
||||
sendAck(task);
|
||||
if (task.leaveAfterAck) {
|
||||
setTimeout(() => {
|
||||
@@ -103,6 +109,12 @@ function flushPendingAcks() {
|
||||
}
|
||||
}
|
||||
|
||||
function flushPendingAcks() {
|
||||
// 每次语音播报完成,只确认一条已播放完成的服务端消息。
|
||||
if (!pendingAcks.length) return;
|
||||
completeAckTask(pendingAcks.shift());
|
||||
}
|
||||
|
||||
function ensureAudioAckListener() {
|
||||
// 复用现有全局 audioEnded 事件,确保 ACK 时机落在播报结束之后。
|
||||
if (audioAckListenerReady) return;
|
||||
@@ -118,12 +130,33 @@ function removeAudioAckListener() {
|
||||
|
||||
function queueAckAfterAudio(message) {
|
||||
// 非 ACK_REQUIRED_TYPES 的消息只打印,不自动确认,避免扩大发送面。
|
||||
if (!ACK_REQUIRED_TYPES.has(message.type) || !message.sequence) return;
|
||||
pendingAcks.push({
|
||||
matchId: pickField(message, "matchId", "match_id") || currentContext?.matchId,
|
||||
if (
|
||||
!ACK_REQUIRED_TYPES.has(message.type) ||
|
||||
message.sequence === undefined ||
|
||||
message.sequence === null ||
|
||||
message.sequence === ""
|
||||
) {
|
||||
return;
|
||||
}
|
||||
const task = {
|
||||
matchId: normalizeId(
|
||||
pickField(message, "matchId", "match_id") || currentContext?.matchId
|
||||
),
|
||||
sequence: message.sequence,
|
||||
leaveAfterAck: message.type === ServerMessageType.SERVER_MSG_MATCH_END,
|
||||
});
|
||||
};
|
||||
|
||||
if (AUTO_ACK_WITHOUT_AUDIO) {
|
||||
console.log(
|
||||
"[match-ws] ack immediately in print-only mode",
|
||||
getServerMessageTypeName(message.type),
|
||||
message.sequence
|
||||
);
|
||||
completeAckTask(task);
|
||||
return;
|
||||
}
|
||||
|
||||
pendingAcks.push(task);
|
||||
console.log(
|
||||
"[match-ws] ack queued until audioEnded",
|
||||
getServerMessageTypeName(message.type),
|
||||
@@ -144,6 +177,11 @@ function handleMessage(data) {
|
||||
const typeName = getServerMessageTypeName(message.type);
|
||||
console.log("[match-ws] message decoded", typeName, message);
|
||||
|
||||
const decodedMatchId = normalizeId(pickField(message, "matchId", "match_id"));
|
||||
if (decodedMatchId && currentContext) {
|
||||
currentContext.matchId = decodedMatchId;
|
||||
}
|
||||
|
||||
if (message.type === ServerMessageType.SERVER_MSG_HEARTBEAT) {
|
||||
sendHeartbeatAck();
|
||||
return;
|
||||
@@ -168,16 +206,22 @@ export function connectMatchWebSocket(options = {}) {
|
||||
// 入口参数来自原 websocket 的比赛服地址通知,不影响原有 websocket 连接。
|
||||
const { serverAddr, matchId, userId, token } = options;
|
||||
const url = normalizeServerUrl(serverAddr, token);
|
||||
const normalizedMatchId = normalizeId(matchId);
|
||||
const normalizedUserId = normalizeId(userId);
|
||||
|
||||
if (!url || !matchId) {
|
||||
if (!url || !normalizedMatchId) {
|
||||
console.log("[match-ws] missing serverAddr or matchId", options);
|
||||
return;
|
||||
}
|
||||
|
||||
if (socket && currentContext?.url === url && currentContext?.matchId === matchId) {
|
||||
if (
|
||||
socket &&
|
||||
currentContext?.url === url &&
|
||||
currentContext?.matchId === normalizedMatchId
|
||||
) {
|
||||
// 同一场比赛同一地址重复通知时不重复建连。
|
||||
console.log("[match-ws] already connected or connecting", {
|
||||
matchId,
|
||||
matchId: normalizedMatchId,
|
||||
url,
|
||||
});
|
||||
return;
|
||||
@@ -188,13 +232,21 @@ export function connectMatchWebSocket(options = {}) {
|
||||
|
||||
manualClose = false;
|
||||
isConnecting = true;
|
||||
currentContext = { serverAddr, matchId, userId, url };
|
||||
currentContext = {
|
||||
serverAddr,
|
||||
matchId: normalizedMatchId,
|
||||
userId: normalizedUserId,
|
||||
url,
|
||||
};
|
||||
ensureAudioAckListener();
|
||||
|
||||
const socketTask = uni.connectSocket({
|
||||
url,
|
||||
success: () => {
|
||||
console.log("[match-ws] connect requested", { matchId, url });
|
||||
console.log("[match-ws] connect requested", {
|
||||
matchId: normalizedMatchId,
|
||||
url,
|
||||
});
|
||||
},
|
||||
fail: (err) => {
|
||||
if (socket !== socketTask) return;
|
||||
@@ -209,7 +261,10 @@ export function connectMatchWebSocket(options = {}) {
|
||||
socketTask.onOpen(() => {
|
||||
if (socket !== socketTask) return;
|
||||
isConnecting = false;
|
||||
console.log("[match-ws] connected", { matchId, url });
|
||||
console.log("[match-ws] connected", {
|
||||
matchId: normalizedMatchId,
|
||||
url,
|
||||
});
|
||||
});
|
||||
|
||||
socketTask.onMessage((res) => {
|
||||
|
||||
Reference in New Issue
Block a user