index.tsx 27 KB

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