index.tsx 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. import { Transition, defineComponent, onMounted, reactive, watch, defineAsyncComponent, computed, onUnmounted } from "vue";
  2. import { connectWebsocket, evaluatingData, handleEndBegin, handleStartBegin, handleStartEvaluat, handleViewReport, startCheckDelay, checkUseEarphone, handleCancelEvaluat, checkMinInterval, handleEndEvaluat } from "/src/view/evaluating";
  3. import Earphone from "./earphone";
  4. import styles from "./index.module.less";
  5. import SoundEffect from "./sound-effect";
  6. import state, { handleRessetState, resetPlaybackToStart, clearSelection, initSetPlayRate, resetBaseRate } from "/src/state";
  7. import { storeData } from "/src/store";
  8. import { browser } from "/src/utils";
  9. import { getNoteByMeasuresSlursStart } from "/src/helpers/formateMusic";
  10. import { Icon, Popup, showToast, closeToast, showLoadingToast } from "vant";
  11. import EvaluatResult from "./evaluat-result";
  12. import EvaluatAudio from "./evaluat-audio";
  13. 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, api_workUpdate } from "/src/helpers/communication";
  14. import EvaluatShare from "./evaluat-share";
  15. import { Vue3Lottie } from "vue3-lottie";
  16. import startData from "./data/start.json";
  17. import startingData from "./data/starting.json";
  18. import iconTastBg from "./icons/task-bg.svg";
  19. import iconEvaluat from "./icons/evaluating.json";
  20. import { headImg } from "/src/page-instrument/header-top/image";
  21. import { api_musicPracticeRecordVideoUpload } from "../api";
  22. import { headTopData } from "../header-top/index";
  23. import { getQuery } from "/src/utils/queryString";
  24. import Countdown from "./countdown";
  25. import { IPostMessage } from "/src/utils/native-message";
  26. import tipErjiBg from "./icons/tip_erji.png"
  27. import tipErjiBtn from "./icons/tip_btn.png"
  28. import SubmitNoDonePop from "./submit-nodone";
  29. import { selfSubmitWorkHome } from "../custom-plugins/work-index";
  30. // const DelayCheck = defineAsyncComponent(() =>
  31. // import('./delay-check')
  32. // )
  33. // frequency 频率, amplitude 振幅, decibels 分贝
  34. type TCriteria = "frequency" | "amplitude" | "decibels";
  35. /**
  36. * 节拍器时长
  37. * 评测模式时,应该传节拍器时长
  38. * 阶段评测时,判断是否从第一小节开始,并且曲子本身含有节拍器,需要传节拍器时长,否则传0
  39. */
  40. let actualBeatLength = 0;
  41. let calculateInfo: any = {};
  42. let checkErjiTimer: any = null
  43. export const reCheckDelay = () => {
  44. evaluatingData.onceErjiPopShow = false;
  45. evaluatingData.needCheckErjiStatus = true;
  46. headTopData.settingMode = false
  47. state.setting.soundEffect = false
  48. api_startDelayCheck({});
  49. }
  50. export default defineComponent({
  51. name: "evaluat-model",
  52. setup() {
  53. const query = getQuery();
  54. const evaluatModel = reactive({
  55. tips: true,
  56. evaluatUpdateAudio: false,
  57. isSaveVideo: state.setting.camera && state.setting.saveToAlbum,
  58. shareMode: false,
  59. showNoDonePop: false, // 提交作业显示未达标确认弹窗
  60. });
  61. /**
  62. * 检测返回
  63. */
  64. const handleDelayBack = () => {
  65. if (query.workRecord) {
  66. evaluatingData.soundEffectMode = false;
  67. api_back();
  68. } else {
  69. evaluatingData.soundEffectMode = false;
  70. // handleRessetState();
  71. // headTopData.modeType = "init";
  72. }
  73. };
  74. /**
  75. * 执行检测
  76. */
  77. const handlePerformDetection = async () => {
  78. console.log(evaluatingData.checkStep, evaluatingData, "检测123");
  79. // 检测完成不检测了
  80. if (evaluatingData.checkEnd) return;
  81. // 延迟检测
  82. if (evaluatingData.checkStep === 0) {
  83. evaluatingData.checkStep = 10;
  84. // 没有设备延迟数据 或 开启了效音 显示检测组件,并持续检测耳机状态
  85. if (state.setting.soundEffect) {
  86. evaluatingData.soundEffectMode = true;
  87. return;
  88. }
  89. // 判断只有开始了设备检测之后才去调用api
  90. if (state.setting.soundEffect) {
  91. const delayData = await api_getDeviceDelay();
  92. // console.log("🚀 ~ delayTime:", delayData);
  93. if (delayData && delayData.content?.value < 0) {
  94. evaluatingData.soundEffectMode = true;
  95. return;
  96. }
  97. }
  98. handlePerformDetection();
  99. return;
  100. }
  101. // 效验完成
  102. if (evaluatingData.checkStep === 10) {
  103. const erji = await checkUseEarphone();
  104. if (!erji) {
  105. evaluatingData.earphoneMode = true;
  106. }
  107. evaluatingData.checkEnd = true;
  108. console.log("检测结束,生成数据");
  109. handleConnect();
  110. }
  111. };
  112. const browserInfo = browser();
  113. /** 是否是节奏练习 */
  114. const isRhythmicExercises = () => {
  115. const examSongName = state.examSongName || "";
  116. return examSongName.indexOf("节奏练习") > -1;
  117. };
  118. /** 获取评测标准 */
  119. const getEvaluationCriteria = () => {
  120. let criteria: TCriteria = "frequency";
  121. // 声部打击乐
  122. if ([23, 113, 121].includes(state.subjectId)) {
  123. criteria = "amplitude";
  124. } else if (isRhythmicExercises()) {
  125. // 分类为节奏练习
  126. criteria = "decibels";
  127. }
  128. return criteria;
  129. };
  130. /** 校验耳机状态 */
  131. const checkEarphoneStatus = async (type?: string) => {
  132. clearTimeout(checkErjiTimer);
  133. checkErjiTimer = null;
  134. if (type !== "start") {
  135. // const erji = await checkUseEarphone();
  136. const res = await getEarphone();
  137. const erji = res?.content?.checkIsWired || false;
  138. // 是否已经提示过耳机弹窗,重新进入评测页面,重置该状态为false,手动关掉耳机弹窗,改变该状态为true,本次评测都不在提示耳机状态弹窗
  139. if (!evaluatingData.onceErjiPopShow) {
  140. evaluatingData.earphoneMode = true;
  141. } else {
  142. clearTimeout(checkErjiTimer);
  143. checkErjiTimer = null;
  144. return;
  145. }
  146. evaluatingData.earPhoneType = res?.content?.type || "";
  147. if (evaluatingData.earPhoneType === "有线耳机") {
  148. clearTimeout(checkErjiTimer);
  149. checkErjiTimer = null;
  150. setTimeout(() => {
  151. evaluatingData.earphoneMode = false;
  152. }, 1500);
  153. } else {
  154. // 如果没有佩戴有限耳机,需要持续检测耳机状态
  155. checkErjiTimer = setTimeout(() => {
  156. checkEarphoneStatus();
  157. }, 1000);
  158. }
  159. }
  160. console.log("检测结束,生成数据", evaluatingData.websocketState, evaluatingData.startBegin, evaluatingData.checkEnd);
  161. handleConnect();
  162. };
  163. /** 生成评测曲谱数据 */
  164. const formatTimes = () => {
  165. console.log('评测111')
  166. let starTime = 0;
  167. let ListenMode = false;
  168. let dontEvaluatingMode = false;
  169. let skip = false;
  170. const datas = [];
  171. let selectTimes = state.times;
  172. // 选段评测前面小节的listen、play标识
  173. let preLyricsContent = ''
  174. let unitTestIdx = 0;
  175. let preTime = 0;
  176. let preTimes = [];
  177. // 系统节拍器时长
  178. actualBeatLength = Math.round((state.times[0].fixtime * 1000) / 1);
  179. // 如果是阶段评测,选取该阶段的times
  180. if (state.isSelectMeasureMode && state.section.length) {
  181. const startIndex = state.times.findIndex((n: any) => n.noteId == state.section[0].noteId);
  182. let endIndex = state.times.findIndex((n: any) => n.noteId == state.section[1].noteId);
  183. endIndex = endIndex < state.section[1].i ? state.section[1].i : endIndex;
  184. if (startIndex > 1) {
  185. // firstNoteTime应该取预备小节的第一个音符的开始播放的时间
  186. const idx = startIndex - 1 - state.times[startIndex - 1].si;
  187. preTime = state.times[idx] ? state.times[idx].time * 1000 : 0;
  188. }
  189. actualBeatLength = startIndex == 0 && state.isOpenMetronome ? actualBeatLength : 0;
  190. // 妙极客的曲子,选择的第一小节,beatLength需要传递fixtime
  191. if (state.isEvxml && startIndex == 0) {
  192. actualBeatLength = Math.round((state.times[0].fixtime * 1000) / 1);
  193. }
  194. selectTimes = state.times.filter((n: any, index: number) => {
  195. return index >= startIndex && index <= endIndex;
  196. });
  197. preTimes = state.times.filter((n: any, index: number) => {
  198. return index < startIndex;
  199. });
  200. unitTestIdx = startIndex;
  201. starTime = selectTimes[0].sourceRelativeTime || selectTimes[0].relativeTime;
  202. }
  203. // 阶段评测beatLength需要加上预备小节的持续时长
  204. actualBeatLength = preTimes.length ? actualBeatLength + preTimes[preTimes.length - 1].relaMeasureLength * 1000 : actualBeatLength;
  205. // 如果是弱起,并且预备小节是第一节
  206. if (state.section.length && state.sectionFirst && state.sectionFirst.measureListIndex == 0 && !state.isEvxml) {
  207. // actualBeatLength = actualBeatLength < Math.round((state.times[0].fixtime * 1000) / 1) ? Math.round((state.times[0].fixtime * 1000) / 1) : actualBeatLength;
  208. }
  209. let firstNoteTime = unitTestIdx > 1 ? preTime : 0;
  210. let measureIndex = -1;
  211. let recordMeasure = -1;
  212. // 如果有mp3节拍器,并且预备小节是第一节,并且从0开始播放,actualBeatLength需要加上mp3节拍器时间
  213. if (state.section.length === 2 && firstNoteTime === 0 && state.section[0]?.MeasureNumberXML === state.firstMeasureNumber + 1 && state.times[0].fixtime) {
  214. actualBeatLength = actualBeatLength + Math.round((state.times[0].fixtime * 1000) / 1)
  215. }
  216. // 找到选段评测,开始小节前面最近的是play或者listen的小节
  217. if (preTimes.length) {
  218. for (let index = preTimes.length-1; index >= 0; index--) {
  219. const item = preTimes[index]
  220. const note = getNoteByMeasuresSlursStart(item)
  221. if (note.formatLyricsEntries.contains('Play') || note.formatLyricsEntries.contains('Play...')) {
  222. preLyricsContent = 'Play'
  223. break
  224. }
  225. if (note.formatLyricsEntries.contains('Listen')) {
  226. preLyricsContent = 'Listen'
  227. break
  228. }
  229. }
  230. preLyricsContent = preLyricsContent ? preLyricsContent : 'Play'
  231. }
  232. for (let index = 0; index < selectTimes.length; index++) {
  233. const item = selectTimes[index];
  234. const note = getNoteByMeasuresSlursStart(item);
  235. // #8701 bug: 评测模式,是以曲谱本身的速度进行评测,所以rate取1,不需要转换
  236. // const rate = state.speed / state.originSpeed;
  237. const rate = state.basePlayRate * state.originAudioPlayRate; // 播放倍率
  238. // const difftime = item.difftime;
  239. const difftime = 0;
  240. const start = difftime + (item.sourceRelativeTime || item.relativeTime) - starTime;
  241. const end = difftime + (item.sourceRelaEndtime || item.relaEndtime) - starTime;
  242. const isStaccato = note.noteElement.voiceEntry.isStaccato();
  243. const noteRate = isStaccato ? 0.5 : 1;
  244. // 如果选段评测,开始小节没有注脚,则取前面最近的小节的注脚
  245. if (index == 0 && !note.formatLyricsEntries.length) {
  246. ListenMode = preLyricsContent === 'Play' ? false : preLyricsContent === 'Listen' ? true : false
  247. }
  248. if (note.formatLyricsEntries.contains("Play") || note.formatLyricsEntries.contains("Play...")) {
  249. ListenMode = false;
  250. }
  251. if (note.formatLyricsEntries.contains("Listen")) {
  252. ListenMode = true;
  253. }
  254. if (note.formatLyricsEntries.contains("纯律结束")) {
  255. dontEvaluatingMode = false;
  256. }
  257. if (note.formatLyricsEntries.contains("纯律")) {
  258. dontEvaluatingMode = true;
  259. }
  260. const nextNote = selectTimes[index + 1];
  261. // console.log("noteinfo", note.noteElement.isRestFlag && !!note.stave && !!nextNote)
  262. if (skip && (note.stave || !item.noteElement.isRestFlag || (nextNote && !nextNote.noteElement.isRestFlag))) {
  263. skip = false;
  264. }
  265. if (note.noteElement.isRestFlag && !!note.stave && !!nextNote && nextNote.noteElement.isRestFlag) {
  266. skip = true;
  267. }
  268. // console.log(note.measureOpenIndex, item.measureOpenIndex, note);
  269. // console.log("skip", skip)
  270. // console.log(end,start,rate,noteRate, '评测')
  271. if (note.measureOpenIndex != recordMeasure) {
  272. measureIndex++;
  273. recordMeasure = note.measureOpenIndex;
  274. }
  275. // 是否是需要延续、不停顿演奏的音符
  276. let isTenutoSound = false;
  277. if (item?.noteElement?.tie && item.noteElement.tie?.StartNote) {
  278. const startId = item.noteElement.tie?.StartNote?.NoteToGraphicalNoteObjectId
  279. isTenutoSound = item.NoteToGraphicalNoteObjectId === startId ? false : true
  280. }
  281. // 音符是否不需要评测
  282. let noteNeedEvaluat = item.hasGraceNote || ListenMode || dontEvaluatingMode || !!item?.voiceEntry?.ornamentContainer || !!item.noteElement?.speedInfo?.startWord?.includes('rit.') || item.skipMode
  283. noteNeedEvaluat = noteNeedEvaluat == true ? true : false;
  284. const data = {
  285. timeStamp: (start * 1000) / rate,
  286. duration: ((end * 1000) / rate - (start * 1000) / rate) * noteRate,
  287. frequency: item.frequency,
  288. nextFrequency: item.nextFrequency,
  289. prevFrequency: item.prevFrequency,
  290. // 重复的情况index会自然累加,render的index是谱面渲染的index
  291. measureIndex: measureIndex,
  292. measureRenderIndex: item.measureListIndex,
  293. // item.MeasureNumberXML >= 1 ? item.MeasureNumberXML - 1 : note.noteElement.sourceMeasure.measureListIndex,
  294. dontEvaluating: noteNeedEvaluat,
  295. musicalNotesIndex: index,
  296. denominator: note.noteElement?.Length.denominator,
  297. // isOrnament: !!note?.voiceEntry?.ornamentContainer,
  298. isTenutoSound,
  299. isStaccato: item?.voiceEntry?.isStaccato ? true : false, // 是否是重音
  300. frequencyList: item.frequencyList, // 如果是和弦音符,需要添加多个音符的频率,用于评测
  301. };
  302. datas.push(data);
  303. }
  304. return {
  305. datas,
  306. firstNoteTime,
  307. };
  308. };
  309. /** 连接websocket */
  310. const handleConnect = async () => {
  311. const behaviorId = localStorage.getItem("behaviorId") || localStorage.getItem("BEHAVIORID") || undefined;
  312. // let rate = state.speed / state.originSpeed;
  313. const rate = state.basePlayRate * state.originAudioPlayRate; // 播放倍率
  314. // rate = parseFloat(rate.toFixed(2));
  315. console.log("速度比例", rate, "速度", state.speed);
  316. calculateInfo = formatTimes();
  317. // 评测的速度,如果是选段,则选选段开头小节的速度
  318. const evaluatSpeed = state.sectionStatus && state.section.length === 2 && state.section[0].measureSpeed ? state.section[0].measureSpeed * state.basePlayRate : state.speed;
  319. evaluatingData.evaluatSpeed = evaluatSpeed;
  320. const content = {
  321. musicXmlInfos: calculateInfo.datas,
  322. subjectId: state.musicalCode,
  323. detailId: state.detailId,
  324. examSongId: state.examSongId,
  325. xmlUrl: state.xmlUrl,
  326. partIndex: state.partIndex,
  327. behaviorId,
  328. platform: browserInfo.ios ? "IOS" : browserInfo.android ? "ANDROID" : "WEB",
  329. clientId: storeData.platformType === "STUDENT" ? "student" : storeData.platformType === "TEACHER" ? "teacher" : "education",
  330. hertz: state.setting.frequency,
  331. reactionTimeMs: state.setting.reactionTimeMs ? Number(state.setting.reactionTimeMs) : 0,
  332. speed: evaluatSpeed,
  333. heardLevel: state.setting.evaluationDifficulty,
  334. // beatLength: Math.round((state.fixtime * 1000) / rate),
  335. beatLength: actualBeatLength / rate,
  336. evaluationCriteria: state.evaluationStandard,
  337. speedRate: parseFloat(rate.toFixed(2)), // 播放倍率
  338. };
  339. await connectWebsocket(content);
  340. // state.playSource = "music";
  341. };
  342. /** 评测结果按钮处理 */
  343. const handleEvaluatResult = (type: "practise" | "tryagain" | "look" | "share" | "update" | "selfCancel" | "submitWork") => {
  344. if (type === "update") {
  345. if (state.isAppPlay) {
  346. evaluatModel.evaluatUpdateAudio = true;
  347. resetPlaybackToStart();
  348. return;
  349. } else if (evaluatingData.resultData?.recordIdStr || evaluatingData.resultData?.recordId) {
  350. const rate = state.basePlayRate * state.originAudioPlayRate; // 播放倍率
  351. // 上传云端
  352. // evaluatModel.evaluatUpdateAudio = true;
  353. api_openAdjustRecording({
  354. recordId: evaluatingData.resultData?.recordIdStr || evaluatingData.resultData?.recordId,
  355. title: state.examSongName || "曲谱演奏",
  356. coverImg: state.coverImg,
  357. speedRate: parseFloat(rate.toFixed(2)), // 播放倍率
  358. musicRenderType: state.musicRenderType,
  359. musicSheetId: state.examSongId,
  360. 'part-index': state.partIndex
  361. });
  362. return;
  363. }
  364. } else if (type === "share") {
  365. // 分享
  366. evaluatModel.shareMode = true;
  367. return;
  368. } else if (type === "look") {
  369. // 跳转
  370. handleViewReport("recordId", "instrument");
  371. return;
  372. } else if (type === "practise") {
  373. // 去练习
  374. handleStartEvaluat();
  375. } else if (type === "tryagain") {
  376. /**
  377. * TODO: 2025.01.20 再来一次改为只是关闭结果弹窗,需要用户手动点击评测按钮触发评测,不自动进行评测,故注释掉下方自动评测的方法
  378. */
  379. // startBtnHandle();
  380. } else if (type === "selfCancel") {
  381. // 再来一次,需要手动取消评测,不生成评测记录,不显示评测结果弹窗
  382. evaluatingData.oneselfCancleEvaluating = true;
  383. // handleCancelEvaluat();
  384. handleEndEvaluat(false, 'selfCancel');
  385. // evaluatingData.isBeginMask = true;
  386. evaluatingData.evaluatings = {};
  387. state.playState = "paused";
  388. } else if (type === "submitWork") {
  389. // 作业模式,提交作业,作业没有达标时,提交作业需要弹窗提醒
  390. if (!state.isWorkDone) {
  391. evaluatModel.showNoDonePop = true;
  392. return;
  393. } else {
  394. submitWorkHome();
  395. }
  396. }
  397. resetPlaybackToStart();
  398. evaluatingData.resulstMode = false;
  399. };
  400. // 关闭提交作业确认弹窗
  401. const handleCloseSubmitPop = (type: "again" | "confirm") => {
  402. evaluatModel.showNoDonePop = false;
  403. if (type === "again") {
  404. handleEvaluatResult("tryagain");
  405. } else {
  406. submitWorkHome();
  407. resetPlaybackToStart();
  408. evaluatingData.resulstMode = false;
  409. }
  410. }
  411. // 提交作业
  412. const submitWorkHome = async () => {
  413. // 分为开了摄像头和没开摄像头的情况
  414. if (state.setting.camera) {
  415. const res = await api_workUpdate();
  416. console.log('提交作业回调',res)
  417. if (res) {
  418. if (res?.content?.type === "success") {
  419. handleSaveResult({
  420. id: evaluatingData.resultData?.recordId,
  421. videoFilePath: res?.content?.filePath,
  422. });
  423. // 手动提交评测作业
  424. selfSubmitWorkHome();
  425. } else if (res?.content?.type === "error") {
  426. showToast({
  427. message: res.content?.message || "上传失败",
  428. });
  429. }
  430. }
  431. } else {
  432. // 手动提交评测作业
  433. selfSubmitWorkHome();
  434. }
  435. }
  436. /** 上传音视频 */
  437. const hanldeUpdateVideoAndAudio = async (update = false) => {
  438. if (!update) {
  439. evaluatModel.evaluatUpdateAudio = false;
  440. return;
  441. }
  442. if (state.setting.camera && state.setting.saveToAlbum) {
  443. evaluatModel.evaluatUpdateAudio = false;
  444. api_videoUpdate((res: any) => {
  445. if (res) {
  446. if (res?.content?.type === "success") {
  447. handleSaveResult({
  448. id: evaluatingData.resultData?.recordId,
  449. videoFilePath: res?.content?.filePath,
  450. });
  451. } else if (res?.content?.type === "error") {
  452. showToast({
  453. message: res.content?.message || "上传失败",
  454. });
  455. }
  456. }
  457. });
  458. return;
  459. }
  460. evaluatModel.evaluatUpdateAudio = false;
  461. showToast("上传成功");
  462. };
  463. const handleSaveResult = async (_body: any) => {
  464. await api_musicPracticeRecordVideoUpload(_body);
  465. showToast("上传成功");
  466. };
  467. const startBtnHandle = async () => {
  468. // 如果打开了延迟检测开关,需要先发送开始检测的消息
  469. const delayData = await api_getDeviceDelay();
  470. console.log('设备的延迟值',delayData.content?.value)
  471. if (delayData && delayData.content?.value <= 0) {
  472. await api_startDelayCheck({});
  473. return;
  474. }
  475. evaluatingData.needReplayEvaluat = false;
  476. // 选段未完成时,清除选段状态
  477. if (state.sectionStatus && state.section.length < 2) {
  478. clearSelection();
  479. }
  480. // 如果是异常状态,先等待500ms再执行后续流程
  481. if (evaluatingData.isErrorState && !state.setting.soundEffect) {
  482. // console.log('异常流程1')
  483. // showLoadingToast({
  484. // message: "处理中",
  485. // duration: 1000,
  486. // overlay: true,
  487. // overlayClass: styles.scoreMode,
  488. // });
  489. state.loadingText = "处理中…";
  490. state.isLoading = true;
  491. await new Promise<void>((resolve) => {
  492. setTimeout(() => {
  493. // closeToast();
  494. state.isLoading = false;
  495. evaluatingData.isErrorState = false;
  496. // console.log('异常流程2')
  497. resolve();
  498. }, 1000);
  499. });
  500. }
  501. // console.log('异常流程3')
  502. // 非选段状态,从头开始评测,重置速度
  503. if (!state.sectionStatus && state.section.length === 0) {
  504. state.activeNoteIndex = 0;
  505. state.speed = state.times[0].measureSpeed * state.basePlayRate
  506. // console.log('速度',7,state.speed)
  507. }
  508. initSetPlayRate();
  509. // 检测APP端socket状态
  510. const res: any = await startCheckDelay();
  511. if (res?.checked) {
  512. handleConnect();
  513. handleStartBegin(calculateInfo.firstNoteTime);
  514. evaluatingData.resulstMode = false;
  515. if (evaluatingData.isErrorState) {
  516. evaluatingData.isErrorState = false;
  517. // evaluatingData.resulstMode = false;
  518. }
  519. }
  520. };
  521. // 监听到APP取消延迟检测
  522. const handleCancelDelayCheck = async (res?: IPostMessage) => {
  523. console.log("监听取消延迟检测", res);
  524. if (res?.content) {
  525. // 关闭延迟检测页面,并返回到模式选择页面
  526. // await api_closeDelayCheck({});
  527. handleDelayBack();
  528. }
  529. };
  530. // 监听APP延迟成功的回调
  531. const handleFinishDelayCheck = async (res?: IPostMessage) => {
  532. console.log("监听延迟检测成功", res);
  533. evaluatingData.socketErrorPop = false;
  534. if (res?.content) {
  535. evaluatingData.checkEnd = true;
  536. state.setting.soundEffect = false;
  537. evaluatingData.tipErjiShow = true;
  538. }
  539. };
  540. // 监听重复评测消息
  541. const handRetryEvaluating = () => {
  542. handleEvaluatResult("tryagain");
  543. };
  544. const earPhonePopShow = computed(() => {
  545. return evaluatingData.earphoneMode && !state.isLoading && !state.hasDriverPop && !evaluatingData.showOpenCameraPop;
  546. });
  547. const tipErjiPopShow = computed(() => {
  548. return evaluatingData.tipErjiShow && !state.isLoading && !state.hasDriverPop && !evaluatingData.showOpenCameraPop;
  549. });
  550. // watch(
  551. // () => state.setting.soundEffect,
  552. // (val) => {
  553. // if (val) {
  554. // headTopData.settingMode = false
  555. // api_startDelayCheck({});
  556. // state.setting.soundEffect = false
  557. // }
  558. // }
  559. // );
  560. // 手动取消评测,需要自动再次评测
  561. // watch(
  562. // () => evaluatingData.needReplayEvaluat,
  563. // (val) => {
  564. // if (val && evaluatingData.oneselfCancleEvaluating) {
  565. // setTimeout(() => {
  566. // startBtnHandle();
  567. // }, 500);
  568. // }
  569. // }
  570. // );
  571. onMounted(async () => {
  572. // 如果打开了延迟检测开关,需要先发送开始检测的消息
  573. const delayData = await api_getDeviceDelay();
  574. console.log('设备的延迟值',delayData.content?.value)
  575. if (delayData && delayData.content?.value <= 0) {
  576. await api_startDelayCheck({});
  577. } else {
  578. evaluatingData.checkEnd = true;
  579. // 点击评测模式进入评测模块的需要检测耳机状态,通过返回按钮进入评测模块的,不检测耳机状态
  580. if (evaluatingData.needCheckErjiStatus) {
  581. checkEarphoneStatus();
  582. }
  583. }
  584. evaluatingData.isDisabledPlayMusic = true;
  585. // handlePerformDetection();
  586. api_cancelDelayCheck(handleCancelDelayCheck);
  587. api_finishDelayCheck(handleFinishDelayCheck);
  588. api_retryEvaluating(handRetryEvaluating);
  589. });
  590. onUnmounted(() => {
  591. api_remove_finishDelayCheck(handleFinishDelayCheck);
  592. api_remove_cancelDelayCheck(handleCancelDelayCheck);
  593. clearTimeout(checkErjiTimer);
  594. checkErjiTimer = null;
  595. });
  596. // 资源类型
  597. const isPad = navigator?.userAgent?.includes("UAWEIVRD-W09") || browserInfo?.iPad || browserInfo.isTablet;
  598. return () => (
  599. <div>
  600. <div class={styles.operatingBtn}>
  601. {!evaluatingData.startBegin && (
  602. <img
  603. class={[styles.iconBtn, "evaluting-1"]}
  604. src={headImg("icon_play.png")}
  605. onClick={() => {
  606. startBtnHandle();
  607. }}
  608. />
  609. )}
  610. {evaluatingData.startBegin && (
  611. <>
  612. <img class={styles.iconBtn} src={headImg("icon_reset.png")} onClick={() => {
  613. // 校验评测最小间隔时间
  614. const currentTime = +new Date();
  615. // 开始评测和结束评测的间隔时间小于800毫秒,则不处理
  616. if (currentTime - evaluatingData.recordingTime < 800) {
  617. return;
  618. }
  619. handleEvaluatResult("selfCancel")
  620. }} />
  621. <img class={styles.iconBtn} src={headImg("submit.png")} onClick={() => {
  622. // 校验评测最小间隔时间
  623. const currentTime = +new Date();
  624. // 开始评测和结束评测的间隔时间小于800毫秒,则不处理
  625. if (currentTime - evaluatingData.recordingTime < 800) {
  626. return;
  627. }
  628. handleEndBegin()
  629. }} />
  630. </>
  631. )}
  632. </div>
  633. {/* {evaluatingData.soundEffectMode && (
  634. <DelayCheck
  635. onClose={() => {
  636. evaluatingData.soundEffectMode = false;
  637. handlePerformDetection();
  638. }}
  639. onBack={() => handleDelayBack()}
  640. />
  641. )} */}
  642. {/* 倒计时 */}
  643. <Countdown />
  644. {/* 遮罩 */}
  645. {
  646. evaluatingData.isBeginMask && <div class={styles.beginMask}></div>
  647. }
  648. <Popup teleport="body" closeOnClickOverlay={false} class={["popup-custom", "van-scale"]} transition="van-scale" v-model:show={tipErjiPopShow.value}>
  649. <div class={[styles.earphoneBox, isPad && styles.ipadEarphoneBox]}>
  650. <img class={styles.earphoneBg} src={tipErjiBg} />
  651. <img class={styles.earphoneBtn} src={tipErjiBtn} onClick={() => {
  652. evaluatingData.tipErjiShow = false;
  653. checkEarphoneStatus();
  654. }} />
  655. </div>
  656. </Popup>
  657. <Popup teleport="body" closeOnClickOverlay={false} class={["popup-custom", "van-scale"]} transition="van-scale" v-model:show={earPhonePopShow.value}>
  658. <Earphone
  659. earphoneType={evaluatingData.earPhoneType}
  660. onClose={() => {
  661. evaluatingData.onceErjiPopShow = true;
  662. clearTimeout(checkErjiTimer);
  663. checkErjiTimer = null;
  664. // #11035,可能刚好关闭耳机弹窗的时候,第二次又出现了弹窗
  665. setTimeout(() => {
  666. evaluatingData.earphoneMode = false;
  667. }, 300);
  668. // handlePerformDetection();
  669. checkEarphoneStatus("start");
  670. }}
  671. />
  672. </Popup>
  673. {/* 评测作业,非完整评测不显示评测结果弹窗 */}
  674. {
  675. evaluatingData.resulstMode &&
  676. <>
  677. {evaluatingData.hideResultModal ? (
  678. <EvaluatResult onClose={handleEvaluatResult} />
  679. ) : (
  680. <Popup teleport="body" closeOnClickOverlay={false} class={["popup-custom", "van-scale"]} transition="van-scale" v-model:show={evaluatingData.resulstMode}>
  681. <EvaluatResult onClose={handleEvaluatResult} />
  682. </Popup>
  683. )}
  684. </>
  685. }
  686. <Popup teleport="body" closeOnClickOverlay={false} class={["popup-custom", "van-scale"]} transition="van-scale" v-model:show={evaluatModel.evaluatUpdateAudio}>
  687. <EvaluatAudio onClose={hanldeUpdateVideoAndAudio} />
  688. </Popup>
  689. <Popup teleport="body" class={["popup-custom", "van-scale"]} transition="van-scale" v-model:show={evaluatModel.shareMode}>
  690. <EvaluatShare onClose={() => (evaluatModel.shareMode = false)} />
  691. </Popup>
  692. <Popup teleport="body" class={["popup-custom", "van-scale"]} transition="van-scale" v-model:show={evaluatModel.showNoDonePop}>
  693. <SubmitNoDonePop onClose={handleCloseSubmitPop} />
  694. </Popup>
  695. </div>
  696. );
  697. },
  698. });