diff --git a/src/audioManager.js b/src/audioManager.js index 8d742ed..4e46697 100644 --- a/src/audioManager.js +++ b/src/audioManager.js @@ -1,6 +1,34 @@ export const AUDIO_INTERRUPTION_BEGIN_EVENT = "audio-interruption-begin"; export const AUDIO_INTERRUPTION_END_EVENT = "audio-interruption-end"; +const TRAINING_START_AUDIO_KEY_MAP = Object.freeze({ + base: "请于计时前完成指定环数对应箭量", + endurance: "请于计时前完成累计环数和箭量", + precision: "请射箭命中高亮区域", +}); + +export const getTrainingStartAudioKey = (trainingType, fallback = "") => + TRAINING_START_AUDIO_KEY_MAP[trainingType] || fallback; + +export const getPrecisionShotAudioKeys = (shootData, directionText = "") => { + if (!shootData || typeof shootData !== "object") return []; + const ring = Number(shootData.ring); + const directionAudioKey = + shootData.angle !== null && + shootData.angle !== undefined && + directionText + ? `向${directionText}调整` + : ""; + if (!Number.isFinite(ring) || ring <= 0) { + return ["未上靶", directionAudioKey].filter(Boolean); + } + + const ringAudioKey = shootData.ringX === true ? "X环" : `${ring}环`; + // protobuf 的 false 可能不编码;只要不是明确命中,本箭就反馈未命中。 + const hitAudioKey = shootData.ok === true ? "Bingo命中目标" : "未命中"; + return [ringAudioKey, directionAudioKey, hitAudioKey].filter(Boolean); +}; + export const audioFils = { tententen: "https://static.shelingxingqiu.com/shootmini/static/audio/tententen.mp3", 点击按钮: "https://static.shelingxingqiu.com/shootmini/static/audio/%E7%82%B9%E5%87%BB%E6%8C%89%E9%92%AE.mp3", @@ -26,6 +54,16 @@ export const audioFils = { "https://static.shelingxingqiu.com/shootmini/static/audio/%E7%AB%99%E8%B7%9D%E4%B8%8E%E9%9D%B6%E7%BA%B8%E5%90%88%E6%A0%BC.MP3", "请射箭!测试站距与靶纸": "https://static.shelingxingqiu.com/shootmini/static/audio/%E8%AF%B7%E5%B0%84%E7%AE%AD%EF%BC%81%E6%B5%8B%E8%AF%95%E7%AB%99%E8%B7%9D%E4%B8%8E%E9%9D%B6%E7%BA%B8.MP3", + "站距过近,靶纸正确": + "https://static.shelingxingqiu.com/shootmini/static/audio0820/%E7%AB%99%E8%B7%9D%E8%BF%87%E8%BF%91%EF%BC%8C%E9%9D%B6%E7%BA%B8%E6%AD%A3%E7%A1%AE.MP3", + "站距过近,靶纸错误": + "https://static.shelingxingqiu.com/shootmini/static/audio0820/%E7%AB%99%E8%B7%9D%E8%BF%87%E8%BF%91%EF%BC%8C%E9%9D%B6%E7%BA%B8%E9%94%99%E8%AF%AF.MP3", + "站距合格,靶纸正确": + "https://static.shelingxingqiu.com/shootmini/static/audio0820/%E7%AB%99%E8%B7%9D%E5%90%88%E6%A0%BC%EF%BC%8C%E9%9D%B6%E7%BA%B8%E6%AD%A3%E7%A1%AE.MP3", + "站距合格,靶纸错误": + "https://static.shelingxingqiu.com/shootmini/static/audio0820/%E7%AB%99%E8%B7%9D%E5%90%88%E6%A0%BC%EF%BC%8C%E9%9D%B6%E7%BA%B8%E9%94%99%E8%AF%AF.MP3", + "未识别到靶纸,请瞄准靶纸射箭": + "https://static.shelingxingqiu.com/shootmini/static/audio/%E6%9C%AA%E8%AF%86%E5%88%AB%E5%88%B0%E9%9D%B6%E7%BA%B8%EF%BC%8C%E8%AF%B7%E7%9E%84%E5%87%86%E9%9D%B6%E7%BA%B8%E5%B0%84%E7%AE%AD.MP3", "未发现靶纸,请瞄准靶纸射箭": "https://static.shelingxingqiu.com/shootmini/static/audio/%E6%9C%AA%E5%8F%91%E7%8E%B0%E9%9D%B6%E7%BA%B8%EF%BC%8C%E8%AF%B7%E7%9E%84%E5%87%86%E9%9D%B6%E7%BA%B8%E5%B0%84%E7%AE%AD.MP3", 轮到你了: @@ -109,6 +147,16 @@ export const audioFils = { "https://static.shelingxingqiu.com/attachment/2025-11-13/de7kzzllq0futwynso.mp3", 练习开始: "https://static.shelingxingqiu.com/attachment/2025-11-14/de88w0lmmt43nnfmoi.mp3", + 请于计时前完成指定环数对应箭量: + "https://static.shelingxingqiu.com/shootmini/static/audio0820/%E8%AF%B7%E4%BA%8E%E8%AE%A1%E6%97%B6%E5%89%8D%E5%AE%8C%E6%88%90%E6%8C%87%E5%AE%9A%E7%8E%AF%E6%95%B0%E5%AF%B9%E5%BA%94%E7%AE%AD%E9%87%8F.MP3", + 请于计时前完成累计环数和箭量: + "https://static.shelingxingqiu.com/shootmini/static/audio0820/%E8%AF%B7%E4%BA%8E%E8%AE%A1%E6%97%B6%E5%89%8D%E5%AE%8C%E6%88%90%E7%B4%AF%E8%AE%A1%E7%8E%AF%E6%95%B0%E5%92%8C%E7%AE%AD%E9%87%8F.MP3", + 请射箭命中高亮区域: + "https://static.shelingxingqiu.com/shootmini/static/audio0820/%E8%AF%B7%E5%B0%84%E7%AE%AD%E5%91%BD%E4%B8%AD%E9%AB%98%E4%BA%AE%E5%8C%BA%E5%9F%9F.MP3", + Bingo命中目标: + "https://static.shelingxingqiu.com/shootmini/static/audio0820/Bingo%E5%91%BD%E4%B8%AD%E7%9B%AE%E6%A0%87.MP3", + 未命中: + "https://static.shelingxingqiu.com/shootmini/static/audio0820/%E6%9C%AA%E5%91%BD%E4%B8%AD.MP3", 练习结束: "https://static.shelingxingqiu.com/shootmini/static/audio/%E7%BB%83%E4%B9%A0%E7%BB%93%E6%9D%9F.mp3", 射箭声音: @@ -137,17 +185,18 @@ const AUDIO_WARM_PRIORITY_KEYS = [ "射箭无效,距离不足且靶纸错误", "射箭无效,未识别到靶纸", "射箭无效,靶纸错误", - "靶纸不符!请更换靶纸", - "站距与靶纸合格", "请射箭!测试站距与靶纸", + "站距过近,靶纸正确", + "站距过近,靶纸错误", + "站距合格,靶纸正确", + "站距合格,靶纸错误", + "未识别到靶纸,请瞄准靶纸射箭", "中场休息", "下半场开始", "决金箭轮", "请蓝方射箭", "请红方射箭", "距离合格", - "距离不足", - "未发现靶纸,请瞄准靶纸射箭", "未上靶", "X环", ]; diff --git a/src/matchWebsocket.js b/src/matchWebsocket.js index ab82d89..13e0865 100644 --- a/src/matchWebsocket.js +++ b/src/matchWebsocket.js @@ -14,6 +14,10 @@ import { getDistanceCheckAudioKey, getInvalidShotAudioKey, } from "@/util"; +import { + getPrecisionShotAudioKeys, + getTrainingStartAudioKey, +} from "@/audioManager"; import { normalizeId, normalizeMatchInfo, @@ -145,6 +149,15 @@ function buildBusinessMessage(message) { matchInfo.status = 0; } const practiceInfo = normalizePracticeInfo(message.practice_info); + const trainingType = String( + practiceInfo.trainingType || + matchInfo.trainingType || + currentContext?.trainingType || + "" + ).trim(); + if (currentContext && trainingType) { + currentContext.trainingType = trainingType; + } const businessType = getBusinessType(message, matchInfo); if (!businessType) return null; @@ -197,11 +210,15 @@ function buildBusinessMessage(message) { matchWsType: message.type, matchWsTypeName: getServerMessageTypeName(message.type), isSecondHalfStart, + ...(trainingType ? { trainingType } : {}), }; } function buildPracticeSyncMessage(message) { const practiceInfo = normalizePracticeInfo(message.practice_info); + if (currentContext && practiceInfo.trainingType) { + currentContext.trainingType = String(practiceInfo.trainingType).trim(); + } const matchId = normalizeId( pickField(message, "matchId", "match_id") || practiceInfo.id || @@ -368,7 +385,16 @@ function getTestDistanceAudioKeys(shootData) { function getAckAudioKeys(message, businessMessage) { switch (message.type) { case ServerMessageType.SERVER_MSG_MATCH_START: - return [businessMessage?.isSecondHalfStart ? "下半场开始" : "比赛开始"]; + if (businessMessage?.isSecondHalfStart) return ["下半场开始"]; + // 个人训练在开始接口成功后主动播报,不再等待 MATCH_START 触发语音。 + if ( + getTrainingStartAudioKey( + businessMessage?.trainingType || currentContext?.trainingType + ) + ) { + return []; + } + return ["比赛开始"]; case ServerMessageType.SERVER_MSG_NOW_YOU: if (isMeleeMessage(message, businessMessage)) return []; return getNowYouAudioKeys(businessMessage); @@ -379,6 +405,17 @@ function getAckAudioKeys(message, businessMessage) { ) { return []; } + if ( + (businessMessage?.trainingType || currentContext?.trainingType) === + "precision" + ) { + const shootData = businessMessage?.shootData; + const directionText = + shootData?.angle !== null && shootData?.angle !== undefined + ? getDirectionText(shootData.angle) + : ""; + return getPrecisionShotAudioKeys(shootData, directionText); + } return getShootResultAudioKeys(businessMessage?.shootData); case ServerMessageType.SERVER_MSG_MATCH_END: return ["比赛结束"]; @@ -865,6 +902,7 @@ export function connectMatchWebSocket(options = {}) { requestPracticeInfoOnOpen = false, appHideResumable = false, practiceEndAudioKey = "", + trainingType = "", force = false, reconnecting = false, reconnectReason = "", @@ -872,6 +910,7 @@ export function connectMatchWebSocket(options = {}) { const normalizedMatchId = normalizeId(matchId); const normalizedUserId = normalizeId(userId); const normalizedMode = Number(mode); + const normalizedTrainingType = String(trainingType || "").trim(); const incomingUrl = normalizeServerUrl(serverAddr, token); const currentUrlToken = getUrlToken(currentContext?.url); const shouldKeepAuthenticatedUrl = !!( @@ -938,6 +977,9 @@ export function connectMatchWebSocket(options = {}) { requestPracticeInfoOnOpen: requestPracticeInfoOnOpen === true, appHideResumable: appHideResumable === true, practiceEndAudioKey: String(practiceEndAudioKey || "").trim(), + trainingType: + normalizedTrainingType || + (isSameContext ? currentContext?.trainingType || "" : ""), meleeHalfRest: isSameContext ? currentContext?.meleeHalfRest === true : false, diff --git a/src/pages/training/components/ShootProgress.vue b/src/pages/training/components/ShootProgress.vue index abf442d..41bc706 100644 --- a/src/pages/training/components/ShootProgress.vue +++ b/src/pages/training/components/ShootProgress.vue @@ -1,6 +1,6 @@