Merge branch 'feat-yuyin' into test
This commit is contained in:
+53
-4
@@ -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环",
|
||||
];
|
||||
|
||||
+43
-1
@@ -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,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, onBeforeUnmount, computed } from "vue";
|
||||
import audioManager from "@/audioManager";
|
||||
import audioManager, { getTrainingStartAudioKey } from "@/audioManager";
|
||||
import { MESSAGETYPESV2 } from "@/constants";
|
||||
import {
|
||||
getDirectionText,
|
||||
@@ -207,7 +207,10 @@ async function onReceiveMessage(msg) {
|
||||
if (Array.isArray(msg)) return;
|
||||
if (msg.type === MESSAGETYPESV2.BattleStart) {
|
||||
halfTime.value = false;
|
||||
audioManager.play("比赛开始");
|
||||
// 已实现的个人训练由页面播放专属提示,避免重复播报。
|
||||
if (!getTrainingStartAudioKey(props.trainingType)) {
|
||||
audioManager.play("比赛开始");
|
||||
}
|
||||
} else if (msg.type === MESSAGETYPESV2.BattleEnd) {
|
||||
audioManager.play("练习结束", false);
|
||||
} else if (msg.type === MESSAGETYPESV2.ShootResult) {
|
||||
|
||||
@@ -10,7 +10,10 @@ import Avatar from "@/components/Avatar.vue";
|
||||
import BowPower from "@/components/BowPower.vue";
|
||||
import TestDistance from "./components/TestDistance.vue";
|
||||
import BubbleTip from "./components/BubbleTip.vue";
|
||||
import audioManager from "@/audioManager";
|
||||
import audioManager, {
|
||||
getPrecisionShotAudioKeys,
|
||||
getTrainingStartAudioKey,
|
||||
} from "@/audioManager";
|
||||
|
||||
import {
|
||||
createPractiseV2API,
|
||||
@@ -466,16 +469,11 @@ const buildShootResultAudioKeys = (message = {}) => {
|
||||
return [];
|
||||
}
|
||||
|
||||
const keys = [
|
||||
arrow.ring ? `${arrow.ringX ? "X" : arrow.ring}环` : "未上靶",
|
||||
];
|
||||
if (arrow.angle !== null && arrow.angle !== undefined) {
|
||||
keys.push(`向${getDirectionText(arrow.angle)}调整`);
|
||||
}
|
||||
if (arrow.threeConsecutive10Rings === true) {
|
||||
keys.push("tententen");
|
||||
}
|
||||
return keys;
|
||||
const directionText =
|
||||
arrow.angle !== null && arrow.angle !== undefined
|
||||
? getDirectionText(arrow.angle)
|
||||
: "";
|
||||
return getPrecisionShotAudioKeys(arrow, directionText);
|
||||
};
|
||||
|
||||
const playAudioKeysAndWait = (keys) => {
|
||||
@@ -790,6 +788,7 @@ const connectPracticeServer = ({
|
||||
requestPracticeInfoOnOpen: true,
|
||||
appHideResumable: appHideResumable.value,
|
||||
practiceEndAudioKey: "练习结束",
|
||||
trainingType: trainingType.value,
|
||||
});
|
||||
connectionClosed.value = false;
|
||||
return true;
|
||||
@@ -1022,6 +1021,13 @@ onLoad((options = {}) => {
|
||||
};
|
||||
practiseId.value = trainingContext.practiceId || "";
|
||||
serverAddr.value = trainingContext.serverAddr || "";
|
||||
|
||||
const startAudioKey = getTrainingStartAudioKey(trainingParams.value.type);
|
||||
const warmupKeys = [startAudioKey];
|
||||
if (trainingParams.value.type === "precision") {
|
||||
warmupKeys.push("Bingo命中目标", "未命中");
|
||||
}
|
||||
void audioManager.warmKeys(warmupKeys.filter(Boolean));
|
||||
});
|
||||
|
||||
const onReady = async () => {
|
||||
@@ -1051,7 +1057,10 @@ const onReady = async () => {
|
||||
start.value = true;
|
||||
pageStage.value = pageStages.SHOOTING;
|
||||
setPracticeAppHideResumable(true);
|
||||
audioManager.play("练习开始");
|
||||
// 开始接口成功即进入正式训练,直接播放对应提示,避免依赖 BattleStart 消息。
|
||||
audioManager.play(
|
||||
getTrainingStartAudioKey(trainingType.value, "练习开始")
|
||||
);
|
||||
} catch (error) {
|
||||
start.value = false;
|
||||
pageStage.value = pageStages.DISTANCE;
|
||||
|
||||
+6
-6
@@ -357,16 +357,16 @@ export const getShootValidation = (shootData) => {
|
||||
};
|
||||
};
|
||||
|
||||
// 测距阶段提示音:同时校验站距和靶纸,保留旧版本距离不足音频。
|
||||
// 测距阶段提示音:按站距与靶纸组合播放新版测距语音。
|
||||
export const getDistanceCheckAudioKey = (shootData) => {
|
||||
const result = getShootValidation(shootData);
|
||||
if (!result.targetDetected) return "未发现靶纸,请瞄准靶纸射箭";
|
||||
if (!result.targetDetected) return "未识别到靶纸,请瞄准靶纸射箭";
|
||||
if (!result.distanceOk && !result.targetOk) {
|
||||
return "射箭无效,距离不足且靶纸错误";
|
||||
return "站距过近,靶纸错误";
|
||||
}
|
||||
if (!result.targetOk) return "靶纸不符!请更换靶纸";
|
||||
if (result.distanceOk) return "站距与靶纸合格";
|
||||
return "距离不足";
|
||||
if (!result.distanceOk) return "站距过近,靶纸正确";
|
||||
if (!result.targetOk) return "站距合格,靶纸错误";
|
||||
return "站距合格,靶纸正确";
|
||||
};
|
||||
|
||||
export const getDistanceCheckText = (shootData) => {
|
||||
|
||||
Reference in New Issue
Block a user