index.tsx 27 KB

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