|
|
@@ -1,16 +1,16 @@
|
|
|
-import { Transition, defineComponent, onMounted, reactive, watch, defineAsyncComponent, computed } from "vue";
|
|
|
-import { connectWebsocket, evaluatingData, handleEndBegin, handleStartBegin, handleStartEvaluat, handleViewReport, startCheckDelay, checkUseEarphone, handleCancelEvaluat } from "/src/view/evaluating";
|
|
|
+import { Transition, defineComponent, onMounted, reactive, watch, defineAsyncComponent, computed, onUnmounted } from "vue";
|
|
|
+import { connectWebsocket, evaluatingData, handleEndBegin, handleStartBegin, handleStartEvaluat, handleViewReport, startCheckDelay, checkUseEarphone, handleCancelEvaluat, checkMinInterval, handleEndEvaluat } from "/src/view/evaluating";
|
|
|
import Earphone from "./earphone";
|
|
|
import styles from "./index.module.less";
|
|
|
import SoundEffect from "./sound-effect";
|
|
|
-import state, { handleRessetState, resetPlaybackToStart, musicalInstrumentCodeInfo } from "/src/state";
|
|
|
+import state, { handleRessetState, resetPlaybackToStart, musicalInstrumentCodeInfo, clearSelection, initSetPlayRate, resetBaseRate } from "/src/state";
|
|
|
import { storeData } from "/src/store";
|
|
|
import { browser } from "/src/utils";
|
|
|
import { getNoteByMeasuresSlursStart } from "/src/helpers/formateMusic";
|
|
|
import { Icon, Popup, showToast, closeToast, showLoadingToast } from "vant";
|
|
|
import EvaluatResult from "./evaluat-result";
|
|
|
import EvaluatAudio from "./evaluat-audio";
|
|
|
-import { api_getDeviceDelay, api_openAdjustRecording, api_proxyServiceMessage, api_videoUpdate, getEarphone, api_back, api_startDelayCheck, api_cancelDelayCheck, api_closeDelayCheck, api_finishDelayCheck, api_retryEvaluating } from "/src/helpers/communication";
|
|
|
+import { api_getDeviceDelay, api_openAdjustRecording, api_proxyServiceMessage, api_videoUpdate, getEarphone, api_back, api_startDelayCheck, api_cancelDelayCheck, api_remove_cancelDelayCheck, api_closeDelayCheck, api_finishDelayCheck, api_retryEvaluating, api_remove_finishDelayCheck } from "/src/helpers/communication";
|
|
|
import EvaluatShare from "./evaluat-share";
|
|
|
import { Vue3Lottie } from "vue3-lottie";
|
|
|
import startData from "./data/start.json";
|
|
|
@@ -23,6 +23,8 @@ import { headTopData } from "../header-top/index";
|
|
|
import { getQuery } from "/src/utils/queryString";
|
|
|
import Countdown from "./countdown";
|
|
|
import { IPostMessage } from "/src/utils/native-message";
|
|
|
+import tipErjiBg from "./icons/tip_erji.png"
|
|
|
+import tipErjiBtn from "./icons/tip_btn.png"
|
|
|
|
|
|
// const DelayCheck = defineAsyncComponent(() =>
|
|
|
// import('./delay-check')
|
|
|
@@ -38,6 +40,13 @@ type TCriteria = "frequency" | "amplitude" | "decibels";
|
|
|
*/
|
|
|
let actualBeatLength = 0;
|
|
|
let calculateInfo: any = {};
|
|
|
+let checkErjiTimer: any = null
|
|
|
+
|
|
|
+export const reCheckDelay = () => {
|
|
|
+ headTopData.settingMode = false
|
|
|
+ state.setting.soundEffect = false
|
|
|
+ api_startDelayCheck({});
|
|
|
+}
|
|
|
|
|
|
export default defineComponent({
|
|
|
name: "evaluat-model",
|
|
|
@@ -58,8 +67,8 @@ export default defineComponent({
|
|
|
api_back();
|
|
|
} else {
|
|
|
evaluatingData.soundEffectMode = false;
|
|
|
- handleRessetState();
|
|
|
- headTopData.modeType = "init";
|
|
|
+ // handleRessetState();
|
|
|
+ // headTopData.modeType = "init";
|
|
|
}
|
|
|
};
|
|
|
/**
|
|
|
@@ -125,6 +134,8 @@ export default defineComponent({
|
|
|
|
|
|
/** 校验耳机状态 */
|
|
|
const checkEarphoneStatus = async (type?: string) => {
|
|
|
+ clearTimeout(checkErjiTimer);
|
|
|
+ checkErjiTimer = null;
|
|
|
if (type !== "start") {
|
|
|
// const erji = await checkUseEarphone();
|
|
|
const res = await getEarphone();
|
|
|
@@ -133,9 +144,16 @@ export default defineComponent({
|
|
|
evaluatingData.earphoneMode = true;
|
|
|
evaluatingData.earPhoneType = res?.content?.type || "";
|
|
|
if (evaluatingData.earPhoneType === "有线耳机") {
|
|
|
+ clearTimeout(checkErjiTimer);
|
|
|
+ checkErjiTimer = null;
|
|
|
setTimeout(() => {
|
|
|
evaluatingData.earphoneMode = false;
|
|
|
}, 3000);
|
|
|
+ } else {
|
|
|
+ // 如果没有佩戴有限耳机,需要持续检测耳机状态
|
|
|
+ checkErjiTimer = setTimeout(() => {
|
|
|
+ checkEarphoneStatus();
|
|
|
+ }, 1000);
|
|
|
}
|
|
|
}
|
|
|
console.log("检测结束,生成数据", evaluatingData.websocketState, evaluatingData.startBegin, evaluatingData.checkEnd);
|
|
|
@@ -144,6 +162,7 @@ export default defineComponent({
|
|
|
|
|
|
/** 生成评测曲谱数据 */
|
|
|
const formatTimes = () => {
|
|
|
+ console.log('评测111')
|
|
|
let starTime = 0;
|
|
|
let ListenMode = false;
|
|
|
let dontEvaluatingMode = false;
|
|
|
@@ -177,6 +196,11 @@ export default defineComponent({
|
|
|
}
|
|
|
// 阶段评测beatLength需要加上预备小节的持续时长
|
|
|
actualBeatLength = preTimes.length ? actualBeatLength + preTimes[preTimes.length - 1].relaMeasureLength * 1000 : actualBeatLength;
|
|
|
+ // 如果是弱起,并且预备小节是第一节
|
|
|
+ if (state.section.length && state.sectionFirst && state.sectionFirst.measureListIndex == 0) {
|
|
|
+ actualBeatLength = actualBeatLength < Math.round((state.times[0].fixtime * 1000) / 1) ? Math.round((state.times[0].fixtime * 1000) / 1) : actualBeatLength;
|
|
|
+ }
|
|
|
+
|
|
|
let firstNoteTime = unitTestIdx > 1 ? preTime : 0;
|
|
|
let measureIndex = -1;
|
|
|
let recordMeasure = -1;
|
|
|
@@ -186,8 +210,9 @@ export default defineComponent({
|
|
|
const note = getNoteByMeasuresSlursStart(item);
|
|
|
// #8701 bug: 评测模式,是以曲谱本身的速度进行评测,所以rate取1,不需要转换
|
|
|
// const rate = state.speed / state.originSpeed;
|
|
|
- const rate = 1;
|
|
|
- const difftime = item.difftime;
|
|
|
+ const rate = state.basePlayRate * state.originAudioPlayRate; // 播放倍率
|
|
|
+ // const difftime = item.difftime;
|
|
|
+ const difftime = 0;
|
|
|
const start = difftime + (item.sourceRelativeTime || item.relativeTime) - starTime;
|
|
|
const end = difftime + (item.sourceRelaEndtime || item.relaEndtime) - starTime;
|
|
|
const isStaccato = note.noteElement.voiceEntry.isStaccato();
|
|
|
@@ -225,6 +250,9 @@ export default defineComponent({
|
|
|
const startId = item.noteElement.tie?.StartNote?.NoteToGraphicalNoteObjectId
|
|
|
isTenutoSound = item.NoteToGraphicalNoteObjectId === startId ? false : true
|
|
|
}
|
|
|
+ // 音符是否不需要评测
|
|
|
+ let noteNeedEvaluat = item.hasGraceNote || ListenMode || dontEvaluatingMode || !!item?.voiceEntry?.ornamentContainer || !!item.noteElement?.speedInfo?.startWord?.includes('rit.') || item.skipMode
|
|
|
+ noteNeedEvaluat = noteNeedEvaluat == true ? true : false;
|
|
|
const data = {
|
|
|
timeStamp: (start * 1000) / rate,
|
|
|
duration: ((end * 1000) / rate - (start * 1000) / rate) * noteRate,
|
|
|
@@ -234,12 +262,13 @@ export default defineComponent({
|
|
|
// 重复的情况index会自然累加,render的index是谱面渲染的index
|
|
|
measureIndex: measureIndex,
|
|
|
measureRenderIndex: item.measureListIndex,
|
|
|
- dontEvaluating: item.hasGraceNote || ListenMode || dontEvaluatingMode || !!item?.voiceEntry?.ornamentContainer || !!item.noteElement?.speedInfo?.startWord?.includes('rit.') || item.skipMode,
|
|
|
+ // item.MeasureNumberXML >= 1 ? item.MeasureNumberXML - 1 : note.noteElement.sourceMeasure.measureListIndex,
|
|
|
+ dontEvaluating: noteNeedEvaluat,
|
|
|
musicalNotesIndex: index,
|
|
|
denominator: note.noteElement?.Length.denominator,
|
|
|
// isOrnament: !!note?.voiceEntry?.ornamentContainer,
|
|
|
isTenutoSound,
|
|
|
- isStaccato: item?.voiceEntry?.isStaccato, // 是否是重音
|
|
|
+ isStaccato: item?.voiceEntry?.isStaccato ? true : false, // 是否是重音
|
|
|
};
|
|
|
datas.push(data);
|
|
|
}
|
|
|
@@ -251,10 +280,14 @@ export default defineComponent({
|
|
|
/** 连接websocket */
|
|
|
const handleConnect = async () => {
|
|
|
const behaviorId = localStorage.getItem("behaviorId") || localStorage.getItem("BEHAVIORID") || undefined;
|
|
|
- let rate = state.speed / state.originSpeed;
|
|
|
- rate = parseFloat(rate.toFixed(2));
|
|
|
+ // let rate = state.speed / state.originSpeed;
|
|
|
+ const rate = state.basePlayRate * state.originAudioPlayRate; // 播放倍率
|
|
|
+ // rate = parseFloat(rate.toFixed(2));
|
|
|
console.log("速度比例", rate, "速度", state.speed);
|
|
|
calculateInfo = formatTimes();
|
|
|
+ // 评测的速度,如果是选段,则选选段开头小节的速度
|
|
|
+ const evaluatSpeed = state.sectionStatus && state.section.length === 2 && state.section[0].measureSpeed ? state.section[0].measureSpeed * state.basePlayRate : state.speed;
|
|
|
+ evaluatingData.evaluatSpeed = evaluatSpeed;
|
|
|
const content = {
|
|
|
musicXmlInfos: calculateInfo.datas,
|
|
|
subjectId: state.musicalCode,
|
|
|
@@ -267,12 +300,12 @@ export default defineComponent({
|
|
|
clientId: storeData.platformType === "STUDENT" ? "student" : storeData.platformType === "TEACHER" ? "teacher" : "education",
|
|
|
hertz: state.setting.frequency,
|
|
|
reactionTimeMs: state.setting.reactionTimeMs ? Number(state.setting.reactionTimeMs) : 0,
|
|
|
- speed: state.speed,
|
|
|
+ speed: evaluatSpeed,
|
|
|
heardLevel: state.setting.evaluationDifficulty,
|
|
|
// beatLength: Math.round((state.fixtime * 1000) / rate),
|
|
|
beatLength: actualBeatLength,
|
|
|
evaluationCriteria: state.evaluationStandard,
|
|
|
- speedRate: rate, // 播放倍率
|
|
|
+ speedRate: parseFloat(rate.toFixed(2)), // 播放倍率
|
|
|
};
|
|
|
await connectWebsocket(content);
|
|
|
// state.playSource = "music";
|
|
|
@@ -286,15 +319,14 @@ export default defineComponent({
|
|
|
resetPlaybackToStart();
|
|
|
return;
|
|
|
} else if (evaluatingData.resultData?.recordIdStr || evaluatingData.resultData?.recordId) {
|
|
|
- let rate = state.speed / state.originSpeed;
|
|
|
- rate = parseFloat(rate.toFixed(2));
|
|
|
+ const rate = state.basePlayRate * state.originAudioPlayRate; // 播放倍率
|
|
|
// 上传云端
|
|
|
// evaluatModel.evaluatUpdateAudio = true;
|
|
|
api_openAdjustRecording({
|
|
|
recordId: evaluatingData.resultData?.recordIdStr || evaluatingData.resultData?.recordId,
|
|
|
title: state.examSongName || "曲谱演奏",
|
|
|
coverImg: state.coverImg,
|
|
|
- speedRate: rate, // 播放倍率
|
|
|
+ speedRate: parseFloat(rate.toFixed(2)), // 播放倍率
|
|
|
musicRenderType: state.musicRenderType,
|
|
|
musicSheetId: state.examSongId,
|
|
|
'part-index': state.partIndex
|
|
|
@@ -317,8 +349,11 @@ export default defineComponent({
|
|
|
} else if (type === "selfCancel") {
|
|
|
// 再来一次,需要手动取消评测,不生成评测记录,不显示评测结果弹窗
|
|
|
evaluatingData.oneselfCancleEvaluating = true;
|
|
|
- handleCancelEvaluat();
|
|
|
- startBtnHandle();
|
|
|
+ // handleCancelEvaluat();
|
|
|
+ handleEndEvaluat(true, 'selfCancel');
|
|
|
+ // evaluatingData.isBeginMask = true;
|
|
|
+ evaluatingData.evaluatings = {};
|
|
|
+ state.playState = "paused";
|
|
|
}
|
|
|
resetPlaybackToStart();
|
|
|
evaluatingData.resulstMode = false;
|
|
|
@@ -357,6 +392,18 @@ export default defineComponent({
|
|
|
};
|
|
|
|
|
|
const startBtnHandle = async () => {
|
|
|
+ // 如果打开了延迟检测开关,需要先发送开始检测的消息
|
|
|
+ const delayData = await api_getDeviceDelay();
|
|
|
+ console.log('设备的延迟值',delayData.content?.value)
|
|
|
+ if (delayData && delayData.content?.value <= 0) {
|
|
|
+ await api_startDelayCheck({});
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ evaluatingData.needReplayEvaluat = false;
|
|
|
+ // 选段未完成时,清除选段状态
|
|
|
+ if (state.sectionStatus && state.section.length < 2) {
|
|
|
+ clearSelection();
|
|
|
+ }
|
|
|
// 如果是异常状态,先等待500ms再执行后续流程
|
|
|
if (evaluatingData.isErrorState && !state.setting.soundEffect) {
|
|
|
// console.log('异常流程1')
|
|
|
@@ -376,6 +423,12 @@ export default defineComponent({
|
|
|
});
|
|
|
}
|
|
|
// console.log('异常流程3')
|
|
|
+ // 非选段状态,从头开始评测,重置速度
|
|
|
+ if (!state.sectionStatus && state.section.length === 0) {
|
|
|
+ state.activeNoteIndex = 0;
|
|
|
+ state.speed = state.times[0].measureSpeed * state.basePlayRate
|
|
|
+ }
|
|
|
+ initSetPlayRate();
|
|
|
// 检测APP端socket状态
|
|
|
const res: any = await startCheckDelay();
|
|
|
if (res?.checked) {
|
|
|
@@ -401,9 +454,11 @@ export default defineComponent({
|
|
|
// 监听APP延迟成功的回调
|
|
|
const handleFinishDelayCheck = async (res?: IPostMessage) => {
|
|
|
console.log("监听延迟检测成功", res);
|
|
|
+ evaluatingData.socketErrorPop = false;
|
|
|
if (res?.content) {
|
|
|
evaluatingData.checkEnd = true;
|
|
|
- checkEarphoneStatus();
|
|
|
+ state.setting.soundEffect = false;
|
|
|
+ evaluatingData.tipErjiShow = true;
|
|
|
}
|
|
|
};
|
|
|
|
|
|
@@ -413,12 +468,41 @@ export default defineComponent({
|
|
|
};
|
|
|
|
|
|
const earPhonePopShow = computed(() => {
|
|
|
- return evaluatingData.earphoneMode && state.audioDone && !state.hasDriverPop;
|
|
|
+ return evaluatingData.earphoneMode && !state.isLoading && !state.hasDriverPop;
|
|
|
+ });
|
|
|
+
|
|
|
+ const tipErjiPopShow = computed(() => {
|
|
|
+ return evaluatingData.tipErjiShow && !state.isLoading && !state.hasDriverPop;
|
|
|
});
|
|
|
|
|
|
+ // watch(
|
|
|
+ // () => state.setting.soundEffect,
|
|
|
+ // (val) => {
|
|
|
+ // if (val) {
|
|
|
+ // headTopData.settingMode = false
|
|
|
+ // api_startDelayCheck({});
|
|
|
+ // state.setting.soundEffect = false
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // );
|
|
|
+
|
|
|
+ // 手动取消评测,需要自动再次评测
|
|
|
+ // watch(
|
|
|
+ // () => evaluatingData.needReplayEvaluat,
|
|
|
+ // (val) => {
|
|
|
+ // if (val && evaluatingData.oneselfCancleEvaluating) {
|
|
|
+ // setTimeout(() => {
|
|
|
+ // startBtnHandle();
|
|
|
+ // }, 500);
|
|
|
+ // }
|
|
|
+ // }
|
|
|
+ // );
|
|
|
+
|
|
|
onMounted(async () => {
|
|
|
// 如果打开了延迟检测开关,需要先发送开始检测的消息
|
|
|
- if (state.setting.soundEffect) {
|
|
|
+ const delayData = await api_getDeviceDelay();
|
|
|
+ console.log('设备的延迟值',delayData.content?.value)
|
|
|
+ if (delayData && delayData.content?.value <= 0) {
|
|
|
await api_startDelayCheck({});
|
|
|
} else {
|
|
|
evaluatingData.checkEnd = true;
|
|
|
@@ -430,10 +514,18 @@ export default defineComponent({
|
|
|
api_finishDelayCheck(handleFinishDelayCheck);
|
|
|
api_retryEvaluating(handRetryEvaluating);
|
|
|
});
|
|
|
+
|
|
|
+ onUnmounted(() => {
|
|
|
+ api_remove_finishDelayCheck(handleFinishDelayCheck);
|
|
|
+ api_remove_cancelDelayCheck(handleCancelDelayCheck);
|
|
|
+ clearTimeout(checkErjiTimer);
|
|
|
+ checkErjiTimer = null;
|
|
|
+ });
|
|
|
+
|
|
|
return () => (
|
|
|
<div>
|
|
|
<div class={styles.operatingBtn}>
|
|
|
- {evaluatingData.websocketState && !evaluatingData.startBegin && evaluatingData.checkEnd && (
|
|
|
+ {!evaluatingData.startBegin && (
|
|
|
<img
|
|
|
class={[styles.iconBtn, "evaluting-1"]}
|
|
|
src={headImg("icon_play.png")}
|
|
|
@@ -442,10 +534,26 @@ export default defineComponent({
|
|
|
}}
|
|
|
/>
|
|
|
)}
|
|
|
- {evaluatingData.websocketState && evaluatingData.startBegin && (
|
|
|
+ {evaluatingData.startBegin && (
|
|
|
<>
|
|
|
- <img class={styles.iconBtn} src={headImg("icon_reset.png")} onClick={() => handleEvaluatResult("selfCancel")} />
|
|
|
- <img class={styles.iconBtn} src={headImg("submit.png")} onClick={() => handleEndBegin()} />
|
|
|
+ <img class={styles.iconBtn} src={headImg("icon_reset.png")} onClick={() => {
|
|
|
+ // 校验评测最小间隔时间
|
|
|
+ const currentTime = +new Date();
|
|
|
+ // 开始评测和结束评测的间隔时间小于800毫秒,则不处理
|
|
|
+ if (currentTime - evaluatingData.recordingTime < 800) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ handleEvaluatResult("selfCancel")
|
|
|
+ }} />
|
|
|
+ <img class={styles.iconBtn} src={headImg("submit.png")} onClick={() => {
|
|
|
+ // 校验评测最小间隔时间
|
|
|
+ const currentTime = +new Date();
|
|
|
+ // 开始评测和结束评测的间隔时间小于800毫秒,则不处理
|
|
|
+ if (currentTime - evaluatingData.recordingTime < 800) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ handleEndBegin()
|
|
|
+ }} />
|
|
|
</>
|
|
|
)}
|
|
|
</div>
|
|
|
@@ -466,11 +574,25 @@ export default defineComponent({
|
|
|
{
|
|
|
evaluatingData.isBeginMask && <div class={styles.beginMask}></div>
|
|
|
}
|
|
|
+ <Popup teleport="body" closeOnClickOverlay={false} class={["popup-custom", "van-scale"]} transition="van-scale" v-model:show={tipErjiPopShow.value}>
|
|
|
+ <div class={styles.earphoneBox}>
|
|
|
+ <img class={styles.earphoneBg} src={tipErjiBg} />
|
|
|
+ <img class={styles.earphoneBtn} src={tipErjiBtn} onClick={() => {
|
|
|
+ evaluatingData.tipErjiShow = false;
|
|
|
+ checkEarphoneStatus();
|
|
|
+ }} />
|
|
|
+ </div>
|
|
|
+ </Popup>
|
|
|
<Popup teleport="body" closeOnClickOverlay={false} class={["popup-custom", "van-scale"]} transition="van-scale" v-model:show={earPhonePopShow.value}>
|
|
|
<Earphone
|
|
|
earphoneType={evaluatingData.earPhoneType}
|
|
|
onClose={() => {
|
|
|
- evaluatingData.earphoneMode = false;
|
|
|
+ clearTimeout(checkErjiTimer);
|
|
|
+ checkErjiTimer = null;
|
|
|
+ // #11035,可能刚好关闭耳机弹窗的时候,第二次又出现了弹窗
|
|
|
+ setTimeout(() => {
|
|
|
+ evaluatingData.earphoneMode = false;
|
|
|
+ }, 300);
|
|
|
// handlePerformDetection();
|
|
|
checkEarphoneStatus("start");
|
|
|
}}
|