index.tsx 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881
  1. import styles from "./index.module.less";
  2. import { Snackbar } from "@varlet/ui";
  3. import { closeToast, showLoadingToast, showToast, Popup } from "vant";
  4. import { defineComponent, onMounted, onUnmounted, reactive, ref, watch } from "vue";
  5. import { getLeveByScore, getLeveByScoreMeasure, IEvaluatings } from "./evaluatResult";
  6. import {
  7. cancelEvaluating,
  8. endEvaluating,
  9. endSoundCheck,
  10. getEarphone,
  11. api_proxyServiceMessage,
  12. removeResult,
  13. sendResult,
  14. startEvaluating,
  15. startSoundCheck,
  16. api_openWebView,
  17. api_startRecording,
  18. api_startRecordingCb,
  19. api_stopRecording,
  20. api_recordStartTime,
  21. api_remove_recordStartTime,
  22. api_startCapture,
  23. api_endCapture,
  24. api_getDeviceDelay,
  25. hideComplexButton,
  26. api_checkSocketStatus,
  27. addAccompanyError,
  28. removeAccompanyError,
  29. addSocketStatus,
  30. removeSocketStatus,
  31. api_disconnectSocket,
  32. api_midiMicDelay,
  33. api_cloudSetCurrentTime,
  34. api_cloudChangeSpeed,
  35. api_startDelayCheck,
  36. api_closeDelayCheck,
  37. api_openCamera,
  38. api_closeCamera,
  39. api_recordAudioUpload
  40. } from "/src/helpers/communication";
  41. import state, { IPlayState, clearSelection, handleStopPlay, onPlay, resetPlaybackToStart, togglePlay, initSetPlayRate, resetBaseRate, scrollViewNote } from "/src/state";
  42. import { IPostMessage } from "/src/utils/native-message";
  43. import { usePageVisibility } from "@vant/use";
  44. import { browser } from "/src/utils";
  45. import { getAudioCurrentTime, toggleMutePlayAudio, audioListStart, audioData } from "../audio-list";
  46. import { handleStartTick, closeTick } from "../tick";
  47. import AbnormalPop from "../abnormal-pop";
  48. import { storeData } from "../../store";
  49. import icon_bg from "../abnormal-pop/icon_bg.svg";
  50. import icon_close from "../abnormal-pop/icon_close.svg";
  51. import icon_btn from "../abnormal-pop/icon_btn.png";
  52. import icon_success from "../abnormal-pop/icon_success.png";
  53. import { data } from "../../page-instrument/custom-plugins/work-index";
  54. import { startCountdown } from "/src/page-instrument/evaluat-model/countdown";
  55. const browserInfo = browser();
  56. let socketStartTime = 0;
  57. export const popImgs = {
  58. icon_bg,
  59. icon_close,
  60. icon_btn,
  61. icon_success,
  62. };
  63. export const evaluatingData = reactive({
  64. /** 评测数据 */
  65. contentData: {} as any,
  66. /** 评测模块是否加载完成 */
  67. rendered: false,
  68. earphone: false, // 是否插入耳机
  69. soundEffect: false, // 是否效音
  70. soundEffectFrequency: 0, // 效音频率
  71. checkStep: 0, // 执行步骤
  72. checkEnd: false, // 检测结束
  73. earphoneMode: false, // 耳机弹窗
  74. earPhoneType: "" as "" | "有线耳机" | "蓝牙耳机", // 耳机类型
  75. soundEffectMode: false, // 效音弹窗
  76. websocketState: false, // websocket连接状态
  77. /**是否开始播放 */
  78. startBegin: false, // 开始
  79. backtime: 0, // 延迟时间
  80. /** 已经评测的数据 */
  81. evaluatings: {} as IEvaluatings,
  82. /** 评测结果 */
  83. resultData: {} as any,
  84. /** 评测结果弹窗 */
  85. resulstMode: false,
  86. /** 是否是完整评测 */
  87. isComplete: false,
  88. /** */
  89. isDisabledPlayMusic: false,
  90. /** socket异常状态弹窗 */
  91. socketErrorPop: false,
  92. /** 异常提示 */
  93. errorContents: '',
  94. /** socket异常状态弹窗的状态值 */
  95. socketErrorStatus: 0,
  96. /** 延迟检测,socket状态异常 */
  97. delayCheckSocketError: false,
  98. /** 异常状态,不生成评测记录,不调用保存接口 */
  99. isErrorState: false,
  100. /** accompanyError,错误类型 */
  101. accompanyErrorType: '',
  102. /** app播放结束状态,重新评测需要重置为 */
  103. isAudioPlayEnd: false,
  104. preloadJson: true, // 预加载延迟检测的资源
  105. jsonLoading: true, // 延迟检测的资源加载中状态
  106. jsonLoadDone: true, // 延迟检测的动画dom加载完成状态
  107. hideResultModal: false, // 评测作业,如果不是完整评测,需要隐藏评测结果弹窗
  108. oneselfCancleEvaluating: false, // 是否是自主取消评测,自主取消评测,不生产评测记录
  109. isBeginMask: false, // 倒计时和系统节拍器时候的遮罩,防止用户点击,
  110. recordingTime: 0, // 调用startRecording的时间
  111. endEvaluatingTime: 0, // 调用endEvaluating的时间
  112. evaluatSpeed: 0, // 评测记录的速度
  113. needReplayEvaluat: false, // 手动取消评测,需要自动开始评测
  114. needPlayTick: false, // 评测时,mp3节拍器需要等待音频开始播放后再执行播放节拍器的圆点动画
  115. tipErjiShow: false, // 评测提示弹窗
  116. onceErjiPopShow: false, // 是否已经提示过耳机弹窗,重新进入评测页面,重置该状态为false,手动关掉耳机弹窗,改变该状态为true,本次评测都不在提示耳机状态弹窗
  117. needCheckErjiStatus: true, // 点击评测模式进入评测模块的需要检测耳机状态,通过返回按钮进入评测模块的,不检测耳机状态
  118. evaluatResultLoading: false, // 评测结果处理中
  119. });
  120. const sendOffsetTime = async (offsetTime: number) => {
  121. const delayData = await api_getDeviceDelay();
  122. api_midiMicDelay({
  123. header: {
  124. commond: "audioPlayStart",
  125. type: "SOUND_COMPARE",
  126. },
  127. body: {
  128. offsetTime,
  129. micDelay: delayData?.content?.value,
  130. },
  131. });
  132. };
  133. /** 点击开始评测按钮 */
  134. export const handleStartEvaluat = async () => {
  135. if (state.modeType === "evaluating") {
  136. handleCancelEvaluat();
  137. // 放下面会在异步之后执行 旋律线可能在会隐藏不了
  138. state.modeType = "practise";
  139. } else {
  140. // 放下面会在异步之后执行 旋律线可能在会隐藏不了
  141. state.modeType = "evaluating";
  142. if (state.platform !== "PC") {
  143. // 评测前先检查APP端的websocket状态
  144. const res = await api_checkSocketStatus();
  145. if (res?.content?.status === "connected") {
  146. handleStopPlay();
  147. } else {
  148. // socket未连接
  149. // evaluatingData.socketErrorPop = true
  150. }
  151. } else {
  152. handleStopPlay();
  153. }
  154. }
  155. //state.modeType = state.modeType === "evaluating" ? "practise" : "evaluating";
  156. if (state.modeType !== "evaluating") {
  157. // 切换到练习模式,卸载评测模块
  158. evaluatingData.rendered = false;
  159. }
  160. };
  161. /** 开始评测 & 延迟检测开始按钮 */
  162. export const startCheckDelay = async () => {
  163. // 评测前先检查APP端的websocket状态
  164. const res = await api_checkSocketStatus();
  165. if (res?.content?.status === "connected") {
  166. //
  167. return new Promise((resolve) => {
  168. resolve({ checked: true });
  169. });
  170. } else {
  171. /**
  172. * socket未连接,记录此时的时间,以便于和收到socket成功链接,进行对比,对比时间小于500ms时,则连接中的状态默认显示500ms持续时间
  173. *
  174. * */
  175. socketStartTime = +new Date();
  176. evaluatingData.socketErrorPop = true;
  177. evaluatingData.socketErrorStatus = 1;
  178. return new Promise((resolve) => {
  179. resolve({ checked: false });
  180. });
  181. }
  182. };
  183. const check_currentTime = () => {
  184. let preTime = 0;
  185. // 选段评测模式
  186. if (state.isSelectMeasureMode) {
  187. preTime = state.section[0].time * 1000;
  188. }
  189. const currentTime = getAudioCurrentTime() * 1000 - preTime;
  190. // console.log('播放进度music', currentTime, 'preTime:' + preTime)
  191. if (currentTime >= 500) {
  192. sendEvaluatingOffsetTime(500);
  193. return;
  194. }
  195. setTimeout(() => {
  196. check_currentTime();
  197. }, 10);
  198. };
  199. /** 开始播放发送延迟时间 */
  200. export const sendEvaluatingOffsetTime = async (currentTime: number) => {
  201. // 没有开始时间点, 不处理
  202. if (!evaluatingData.backtime) return;
  203. const nowTime = Date.now();
  204. const delayTime = nowTime - evaluatingData.backtime - currentTime;
  205. console.error("真正播放延迟", delayTime, "currentTime:", currentTime);
  206. await api_proxyServiceMessage({
  207. header: {
  208. commond: "audioPlayStart",
  209. type: "SOUND_COMPARE",
  210. },
  211. body: {
  212. offsetTime: delayTime < 0 ? 0 : delayTime,
  213. micDelay: 0,
  214. },
  215. });
  216. };
  217. /** 检测耳机 */
  218. export const checkUseEarphone = async () => {
  219. const res = await getEarphone();
  220. return res?.content?.checkIsWired || false;
  221. };
  222. /**
  223. * 开始录音
  224. */
  225. const handleStartSoundCheck = () => {
  226. startSoundCheck();
  227. };
  228. /** 结束录音 */
  229. export const handleEndSoundCheck = () => {
  230. endSoundCheck();
  231. };
  232. /** 连接websocket */
  233. export const connectWebsocket = async (content: any) => {
  234. evaluatingData.contentData = content;
  235. evaluatingData.websocketState = true;
  236. };
  237. /**
  238. * 执行检测
  239. */
  240. export const handlePerformDetection = async () => {
  241. // 检测完成不检测了
  242. if (evaluatingData.checkEnd) return;
  243. // 延迟检测
  244. if (evaluatingData.checkStep === 0) {
  245. evaluatingData.checkStep = 5;
  246. // 没有设备延迟数据 或 开启了效音 显示检测组件,并持续检测耳机状态
  247. if (state.setting.soundEffect) {
  248. evaluatingData.soundEffectMode = true;
  249. return;
  250. }
  251. const delayTime = await api_getDeviceDelay();
  252. console.log("🚀 ~ delayTime:", delayTime);
  253. if (!delayTime) {
  254. evaluatingData.soundEffectMode = true;
  255. return;
  256. }
  257. handlePerformDetection();
  258. return;
  259. }
  260. // 检测耳机
  261. if ((evaluatingData.checkStep = 5)) {
  262. evaluatingData.checkStep = 10;
  263. const erji = await checkUseEarphone();
  264. if (!erji) {
  265. evaluatingData.earphoneMode = true;
  266. return;
  267. }
  268. handlePerformDetection();
  269. return;
  270. }
  271. // 效音
  272. // if (evaluatingData.checkStep === 7) {
  273. // // 是否需要开启效音
  274. // evaluatingData.checkStep = 10;
  275. // if (state.setting.soundEffect && !state.isPercussion) {
  276. // evaluatingData.soundEffectMode = true;
  277. // handleStartSoundCheck();
  278. // return
  279. // }
  280. // handlePerformDetection();
  281. // return;
  282. // }
  283. // 效验完成
  284. if (evaluatingData.checkStep === 10) {
  285. evaluatingData.checkEnd = true;
  286. }
  287. };
  288. /** 记录小节分数 */
  289. export const addMeasureScore = (measureScore: any, show = true) => {
  290. // #8720 bug修复
  291. for (let idx in evaluatingData.evaluatings) {
  292. evaluatingData.evaluatings[idx].show = false;
  293. }
  294. evaluatingData.evaluatings[measureScore.measureRenderIndex] = {
  295. ...measureScore,
  296. leve: getLeveByScoreMeasure(measureScore.score),
  297. show,
  298. };
  299. // console.log("🚀 ~ measureScore:", evaluatingData.evaluatings)
  300. };
  301. const handleScoreResult = async (res?: IPostMessage) => {
  302. console.log("返回", res, evaluatingData.oneselfCancleEvaluating);
  303. // 如果是手动取消评测,不生成评测记录
  304. // if (evaluatingData.oneselfCancleEvaluating) {
  305. // return;
  306. // }
  307. if (res?.content) {
  308. const { header, body } = res.content;
  309. // 效音返回
  310. if (header.commond === "checking") {
  311. evaluatingData.soundEffectFrequency = body.frequency;
  312. }
  313. // 小节评分返回
  314. if (header?.commond === "measureScore" && !evaluatingData.oneselfCancleEvaluating) {
  315. console.log("🚀 ~ 评测返回:", res);
  316. addMeasureScore(body);
  317. }
  318. // 评测结束返回
  319. if (header?.commond === "overall") {
  320. console.log("🚀 ~ 评测返回:", res);
  321. console.log("评测结束", body);
  322. // 如果评测结果没有返回音频,需要调用api通知APP端上传音频
  323. if (!body.url) {
  324. await api_recordAudioUpload()
  325. }
  326. state.isHideEvaluatReportSaveBtn = false;
  327. setTimeout(() => {
  328. // 评测作业,如果不是完整评测,不展示评测弹窗
  329. if (data.trainingType === "EVALUATION" && !evaluatingData.isComplete) {
  330. evaluatingData.hideResultModal = true;
  331. } else {
  332. evaluatingData.hideResultModal = false;
  333. }
  334. // 手动取消评测,不展示评测弹窗
  335. if (evaluatingData.oneselfCancleEvaluating) {
  336. evaluatingData.hideResultModal = true;
  337. }
  338. evaluatingData.resulstMode = evaluatingData.isErrorState ? false : true;
  339. evaluatingData.startBegin = false;
  340. evaluatingData.evaluatResultLoading = false;
  341. }, 200);
  342. evaluatingData.resultData = {
  343. ...body,
  344. ...getLeveByScore(body.score),
  345. };
  346. // console.log("🚀 ~ evaluatingData.resultData:", evaluatingData.resultData)
  347. closeToast();
  348. state.isLoading = false
  349. }
  350. }
  351. };
  352. /** 开始评测 */
  353. export const handleStartBegin = async (preTimes?: number) => {
  354. // 滚动到当前小节所在区域
  355. scrollViewNote(true);
  356. evaluatingData.needPlayTick = false;
  357. if (state.isAppPlay) {
  358. await api_cloudSetCurrentTime({
  359. currentTime: 0,
  360. songID: state.examSongId,
  361. })
  362. }
  363. evaluatingData.isComplete = false;
  364. evaluatingData.evaluatings = {};
  365. evaluatingData.resultData = {};
  366. evaluatingData.backtime = 0;
  367. evaluatingData.isAudioPlayEnd = false;
  368. const res = await startEvaluating(evaluatingData.contentData);
  369. if (res?.api !== "startEvaluating") {
  370. Snackbar.error("请在APP端进行评测");
  371. evaluatingData.startBegin = false;
  372. return;
  373. }
  374. if (res?.content?.reson) {
  375. // #10984,APP端会有弹窗提示,不需要再次提示
  376. // showToast(res.content?.des);
  377. evaluatingData.startBegin = false;
  378. return;
  379. }
  380. resetPlaybackToStart();
  381. evaluatingData.startBegin = true;
  382. if (evaluatingData.isDisabledPlayMusic) {
  383. evaluatingData.isBeginMask = true
  384. // 先播放倒计时
  385. await startCountdown()
  386. state.playState = state.playState === "paused" ? "play" : "paused";
  387. // 设置为开始播放时, 如果需要节拍,先播放节拍器
  388. if (state.playState === "play" && (state.playType==="play"&&state.needTick)||(state.playType==="sing"&&state.needSingTick)) {
  389. // 如果是系统节拍器 等系统节拍器播完了再播,如果是mp3节拍器 直接播
  390. if((state.playType==="play" && !state.isOpenMetronome)||(state.playType==="sing" && !state.isSingOpenMetronome)){
  391. /**
  392. * #12291
  393. * 如果是选段评测,并且开始小节不是第一个小节,不需要播放节拍器的圆点动画
  394. */
  395. if (state.section.length && state.section[0]?.MeasureNumberXML !== state.firstMeasureNumber) {
  396. //
  397. } else {
  398. const tickend = await handleStartTick();
  399. console.log("🚀 ~ tickend:", tickend)
  400. // 节拍器返回false, 取消播放
  401. if (!tickend) {
  402. state.playState = "paused";
  403. evaluatingData.startBegin = false;
  404. evaluatingData.isBeginMask = false
  405. return;
  406. }
  407. }
  408. }else{
  409. // handleStartTick()
  410. // 需要等待音频返回进度后再执行节拍器圆点动画
  411. evaluatingData.needPlayTick = true;
  412. }
  413. }
  414. evaluatingData.isBeginMask = false
  415. onPlay();
  416. }
  417. if (evaluatingData.isErrorState) {
  418. state.playState = 'paused';
  419. evaluatingData.startBegin = false;
  420. return
  421. }
  422. //开始录音
  423. // await api_startRecording({
  424. // accompanimentState: state.setting.enableAccompaniment ? 1 : 0,
  425. // firstNoteTime: preTimes || 0,
  426. // });
  427. const rate = state.basePlayRate * state.originAudioPlayRate; // 播放倍率
  428. await api_startRecordingCb({
  429. // accompanimentState: state.setting.enableAccompaniment ? 1 : 0,
  430. accompanimentState: !state.accompany ? 0 : 1, // 评测没有伴奏时,静音播放
  431. firstNoteTime: preTimes || 0,
  432. speedRate: parseFloat(rate.toFixed(2)), // 播放倍率
  433. }, () => {
  434. if (state.isAppPlay) {
  435. setTimeout(() => {
  436. sendOffsetTime(0)
  437. }, 300);
  438. }
  439. })
  440. /**
  441. * 安卓端,快速点击开始评测和结束评测,间隔太小了时,会出现异常情况(音频没有停止播放)
  442. * 记录一下startRecord的开始时间
  443. */
  444. evaluatingData.recordingTime = +new Date();
  445. // 如果开启了摄像头, 开启录制视频
  446. if (state.setting.camera) {
  447. console.log("开始录制视频");
  448. await api_startCapture();
  449. }
  450. // 如果是midi音频评测,需要调用cloudPlay
  451. if (state.isAppPlay) {
  452. await api_cloudChangeSpeed({
  453. speed: state.originSpeed,
  454. originalSpeed: state.originSpeed,
  455. songID: state.examSongId,
  456. });
  457. audioData.progress = 0
  458. audioListStart(state.playState);
  459. }
  460. evaluatingData.oneselfCancleEvaluating = false;
  461. };
  462. /** 播放音乐 */
  463. const playMusic = async () => {
  464. const playState = await togglePlay("play");
  465. // 取消播放,停止播放
  466. if (!playState) {
  467. evaluatingData.startBegin = false;
  468. handleCancelEvaluat();
  469. return;
  470. }
  471. // 检测播放进度, 计算延迟
  472. check_currentTime();
  473. // 如果开启了摄像头, 开启录制视频
  474. if (state.setting.camera) {
  475. console.log("开始录制视频");
  476. api_startCapture();
  477. }
  478. };
  479. let _audio: HTMLAudioElement;
  480. /** 录音开始,记录开始时间点 */
  481. const recordStartTimePoint = async (res?: IPostMessage) => {
  482. console.error("开始录音");
  483. // 没有开始评测,不处理
  484. if (!evaluatingData.startBegin) return;
  485. let inteveral = res?.content?.inteveral || 0;
  486. if (browserInfo.ios) {
  487. inteveral *= 1000;
  488. }
  489. evaluatingData.backtime = inteveral || Date.now();
  490. console.log("🚀 ~ 开始时间点:", evaluatingData.backtime, "已经录的时间:", Date.now() - inteveral, "记录时间点:", Date.now());
  491. // 是否禁播
  492. if (evaluatingData.isDisabledPlayMusic) {
  493. return;
  494. }
  495. // 开始播放
  496. playMusic();
  497. };
  498. /**
  499. * 结束评测
  500. * @param isComplete 是否完整评测
  501. * @param endType 结束类型,selfCancel:是否是自己取消本次评测
  502. * @returns
  503. */
  504. export const handleEndEvaluat = (isComplete = false, endType?: string) => {
  505. // 没有开始评测 , 不是评测模式 , 不评分;evaluatResultLoading:评测结果处理中,避免重复结束
  506. if (!evaluatingData.startBegin || state.modeType !== "evaluating" || evaluatingData.evaluatResultLoading) return;
  507. // 结束录音
  508. // api_stopRecording();
  509. // 结束评测
  510. evaluatingData.evaluatResultLoading = true
  511. console.log("评测结束1");
  512. endEvaluating({
  513. musicScoreId: state.examSongId,
  514. });
  515. // 评测作业如果不是完整评测,给出提示
  516. if (!isComplete && data.trainingType === "EVALUATION") {
  517. showToast({
  518. message: "完整演奏结束才算作业分数!",
  519. });
  520. } else {
  521. if (!endType) {
  522. state.loadingText = "正在评分中,请稍等..."
  523. state.isLoading = true
  524. // showLoadingToast({
  525. // message: "评分中",
  526. // duration: 0,
  527. // overlay: true,
  528. // overlayClass: styles.scoreMode,
  529. // });
  530. }
  531. }
  532. setTimeout(() => {
  533. // evaluatingData.startBegin = false;
  534. if (endType === 'selfCancel') {
  535. // 重置播放倍率
  536. const item: any = (state.sectionStatus && state.section.length === 2) ? state.sectionFirst || state.section[0] : state.times[0];
  537. state.activeNoteIndex = item.i;
  538. state.activeMeasureIndex = item.MeasureNumberXML;
  539. resetBaseRate(item.i);
  540. }
  541. }, 500);
  542. evaluatingData.isComplete = isComplete;
  543. // 如果开启了摄像头, 结束录制视频
  544. if (state.setting.camera) {
  545. console.log("结束录制视频");
  546. api_endCapture();
  547. }
  548. };
  549. // 校验评测结束最小的时间间隔
  550. export const checkMinInterval = () => {
  551. const currentTime = +new Date();
  552. // 开始评测和结束评测的间隔时间小于800毫秒,则不处理
  553. if (currentTime - evaluatingData.recordingTime < 800) {
  554. return;
  555. }
  556. }
  557. /**
  558. * 结束评测(手动结束评测)
  559. */
  560. export const handleEndBegin = () => {
  561. handleEndEvaluat();
  562. handleStopPlay();
  563. };
  564. /**
  565. * 取消评测
  566. */
  567. export const handleCancelEvaluat = (cancelType?: string) => {
  568. evaluatingData.evaluatings = {};
  569. evaluatingData.startBegin = false;
  570. // 关闭提示
  571. closeToast();
  572. // 取消记录
  573. api_proxyServiceMessage({
  574. header: {
  575. commond: "recordCancel",
  576. type: "SOUND_COMPARE",
  577. status: 200,
  578. },
  579. });
  580. /**
  581. * 异常状态是取消评测(cancelEvaluating),正常结束时结束评测(endEvaluating)
  582. */
  583. // if (cancelType === "cancel") {
  584. // // 取消评测
  585. // cancelEvaluating();
  586. // } else {
  587. // endEvaluating({
  588. // musicScoreId: state.examSongId,
  589. // });
  590. // }
  591. cancelEvaluating();
  592. // 停止播放
  593. handleStopPlay();
  594. console.log("评测结束2");
  595. endEvaluating({
  596. musicScoreId: state.examSongId,
  597. });
  598. // 如果开启了摄像头, 结束录制视频
  599. if (state.setting.camera) {
  600. console.log("结束录制视频");
  601. api_endCapture();
  602. }
  603. };
  604. /** 查看报告 */
  605. export const handleViewReport = (key: "recordId" | "recordIdStr", type: "gym" | "colexiu" | "orchestra" | "instrument") => {
  606. const id = evaluatingData.resultData?.[key] || "";
  607. let url = "";
  608. switch (type) {
  609. case "gym":
  610. url = location.origin + location.pathname + "#/report/" + id;
  611. break;
  612. case "orchestra":
  613. url = location.origin + location.pathname + "report-share.html?id=" + id;
  614. break;
  615. case "instrument":
  616. url = location.origin + location.pathname + "#/evaluat-report?id=" + id + "&musicRenderType=" + state.musicRenderType + "&systemType=" + state.systemType;
  617. if (state.isSchool) {
  618. url += `&school=1`;
  619. }
  620. break;
  621. default:
  622. url = location.origin + location.pathname + "report-share.html?id=" + id;
  623. break;
  624. }
  625. api_openWebView({
  626. url,
  627. orientation: 0,
  628. isHideTitle: true, // 此处兼容安卓,意思为隐藏全部头部
  629. statusBarTextColor: false,
  630. isOpenLight: true,
  631. c_orientation: 0,
  632. showLoadingAnim: true
  633. });
  634. };
  635. // 隐藏存演奏按钮
  636. const handleComplexButton = (res?: IPostMessage) => {
  637. console.log("监听是否隐藏保存按钮", res);
  638. if (res?.content) {
  639. const { header, body } = res.content;
  640. state.isHideEvaluatReportSaveBtn = true;
  641. }
  642. };
  643. // 检测到APP发送的异常信息
  644. const handleAccompanyError = (res?: IPostMessage) => {
  645. console.log("异常信息返回", res);
  646. if (res?.content) {
  647. const { type, reson } = res.content;
  648. state.playState = 'paused'
  649. switch (type) {
  650. case "enterBackground":
  651. // App退到后台
  652. case "playError":
  653. // 播放异常
  654. case "socketError":
  655. // socket连接断开,评测中,则取消评测
  656. // 延迟检测中
  657. if (evaluatingData.soundEffectMode) {
  658. evaluatingData.socketErrorStatus = 0;
  659. evaluatingData.delayCheckSocketError = true;
  660. evaluatingData.socketErrorPop = type === "socketError" ? true : false;
  661. evaluatingData.accompanyErrorType = type;
  662. // api_checkSocketStatus()
  663. return;
  664. }
  665. // 评测中
  666. if (state.modeType === "evaluating" && evaluatingData.startBegin) {
  667. handleCancelEvaluat("cancel");
  668. }
  669. // 关闭节拍器
  670. closeTick();
  671. // socketerrror,才发送关闭延迟检测的消息
  672. if (type === "socketError") {
  673. api_closeDelayCheck({});
  674. }
  675. evaluatingData.socketErrorStatus = 0;
  676. evaluatingData.socketErrorPop = type === "socketError" ? true : false;
  677. evaluatingData.isErrorState = true;
  678. evaluatingData.accompanyErrorType = type;
  679. resetPlaybackToStart();
  680. break;
  681. case "recordError":
  682. // 录音异常
  683. break;
  684. default:
  685. break;
  686. }
  687. }
  688. };
  689. // 监测socket状态,是否已经成功连接
  690. const handleSocketStatus = (res?: IPostMessage) => {
  691. if (res?.content?.status === "connected") {
  692. const currentTime = +new Date();
  693. evaluatingData.delayCheckSocketError = false;
  694. const diffTime = currentTime - socketStartTime;
  695. if (diffTime < 1000) {
  696. const remainingTime = 1000 - diffTime;
  697. setTimeout(() => {
  698. evaluatingData.socketErrorStatus = 2;
  699. }, remainingTime);
  700. }
  701. }
  702. };
  703. // 评测出现异常,再试一次
  704. export const hanldeConfirmPop = async () => {
  705. api_checkSocketStatus();
  706. evaluatingData.socketErrorStatus = 1;
  707. socketStartTime = +new Date();
  708. };
  709. // 关闭异常弹窗
  710. export const hanldeClosePop = () => {
  711. evaluatingData.socketErrorPop = false;
  712. evaluatingData.socketErrorStatus = 0;
  713. };
  714. export default defineComponent({
  715. name: "evaluating",
  716. setup() {
  717. const pageVisibility = usePageVisibility();
  718. // 需要记录的数据
  719. const record_old_data = reactive({
  720. /** 指法 */
  721. finger: false,
  722. /** 原音伴奏 */
  723. play_mode: "" as IPlayState,
  724. /** 评测是否要伴奏 */
  725. enableAccompaniment: true,
  726. });
  727. /** 记录状态 */
  728. const hanlde_record = () => {
  729. // 取消指法
  730. // record_old_data.finger = state.setting.displayFingering;
  731. // state.setting.displayFingering = false;
  732. // 切换为伴奏
  733. record_old_data.play_mode = state.playSource;
  734. record_old_data.enableAccompaniment = state.setting.enableAccompaniment;
  735. // 如果关闭伴奏,评测静音
  736. if (!record_old_data.enableAccompaniment) {
  737. console.log("关闭伴奏");
  738. toggleMutePlayAudio(record_old_data.play_mode === "music" ? "music" : record_old_data.play_mode === "background" ? "background" : "mingSong", true);
  739. }
  740. };
  741. /** 还原状态 */
  742. const handle_reduction = () => {
  743. // 还原指法
  744. // state.setting.displayFingering = record_old_data.finger;
  745. state.playSource = record_old_data.play_mode;
  746. // 如果关闭伴奏, 结束评测取消静音
  747. if (!record_old_data.enableAccompaniment) {
  748. toggleMutePlayAudio(record_old_data.play_mode === "music" ? "music" : record_old_data.play_mode === "background" ? "background" : "mingSong", false);
  749. }
  750. };
  751. // 打开摄像头
  752. const openSetCamera = async () => {
  753. if (state.setting.camera) {
  754. const res = await api_openCamera();
  755. // 没有授权
  756. if (res?.content?.reson) {
  757. state.setting.camera = false;
  758. store.set("musicscoresetting", state.setting);
  759. }
  760. }
  761. }
  762. watch(pageVisibility, (value) => {
  763. if (value == "hidden" && evaluatingData.startBegin) {
  764. // handleEndBegin();
  765. }
  766. });
  767. watch(
  768. () => evaluatingData.socketErrorStatus,
  769. () => {
  770. if (evaluatingData.socketErrorStatus === 2) {
  771. setTimeout(() => {
  772. evaluatingData.socketErrorPop = false;
  773. // evaluatingData.socketErrorStatus = 0
  774. }, 1000);
  775. }
  776. }
  777. );
  778. watch(
  779. () => evaluatingData.socketErrorPop,
  780. () => {
  781. if (evaluatingData.socketErrorPop && state.setting.soundEffect) {
  782. // 监听到socket状态异常,需要关闭延迟检测
  783. api_closeDelayCheck({});
  784. }
  785. }
  786. );
  787. onMounted(() => {
  788. openSetCamera();
  789. resetPlaybackToStart();
  790. hanlde_record();
  791. evaluatingData.resultData = {};
  792. // evaluatingData.resulstMode = true;
  793. // evaluatingData.resultData = {...getLeveByScore(10), score: 10, intonation: 10, cadence: 30, integrity: 40}
  794. // console.log("🚀 ~ evaluatingData.resultData:", evaluatingData.resultData)
  795. evaluatingData.onceErjiPopShow = false;
  796. evaluatingData.evaluatings = {};
  797. evaluatingData.soundEffectFrequency = 0;
  798. evaluatingData.checkStep = 0;
  799. evaluatingData.rendered = true;
  800. sendResult(handleScoreResult);
  801. hideComplexButton(handleComplexButton, true);
  802. api_recordStartTime(recordStartTimePoint);
  803. addAccompanyError(handleAccompanyError);
  804. addSocketStatus(handleSocketStatus);
  805. // 不是选段模式评测, 就清空已选段
  806. if (!state.isSelectMeasureMode) {
  807. clearSelection();
  808. }
  809. console.log("加载评测模块成功");
  810. });
  811. onUnmounted(() => {
  812. evaluatingData.checkEnd = false;
  813. evaluatingData.rendered = false;
  814. resetPlaybackToStart();
  815. removeResult(handleScoreResult);
  816. hideComplexButton(() => {}, false);
  817. api_remove_recordStartTime(recordStartTimePoint);
  818. handle_reduction();
  819. removeAccompanyError(handleAccompanyError);
  820. if (evaluatingData.socketErrorPop && state.setting.soundEffect) {
  821. console.log('延迟检测出错')
  822. } else {
  823. removeSocketStatus(handleSocketStatus);
  824. }
  825. api_closeCamera();
  826. api_disconnectSocket();
  827. console.log("卸载评测模块成功");
  828. });
  829. return () => (
  830. <div>
  831. {/** 预加载一下断网需要用到的图片 */}
  832. <div class={styles.hiddenPop}>
  833. <img src={popImgs.icon_bg} />
  834. <img src={popImgs.icon_btn} />
  835. <img src={popImgs.icon_success} />
  836. <img src={popImgs.icon_close} />
  837. </div>
  838. <Popup teleport="body" closeOnClickOverlay={false} class={["popup-custom", "van-scale", evaluatingData.socketErrorStatus === 2 && styles.socketErrorStatus]} transition="van-scale" v-model:show={evaluatingData.socketErrorPop} overlay-style={evaluatingData.socketErrorStatus === 2?{ background: "initial" }:{}}>
  839. <AbnormalPop onConfirm={hanldeConfirmPop} onClose={hanldeClosePop} />
  840. </Popup>
  841. </div>
  842. );
  843. },
  844. });