index.tsx 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311
  1. import { Snackbar } from "@varlet/ui";
  2. import { closeToast, showLoadingToast } from "vant";
  3. import { defineComponent, onBeforeUnmount, onMounted, onUnmounted, reactive, ref, watch } from "vue";
  4. import { getLeveByScore, getLeveByScoreMeasure, IEvaluatings } from "./evaluatResult";
  5. import {
  6. cancelEvaluating,
  7. endEvaluating,
  8. endSoundCheck,
  9. getEarphone,
  10. api_proxyServiceMessage,
  11. removeResult,
  12. sendResult,
  13. startEvaluating,
  14. startSoundCheck,
  15. api_openWebView,
  16. api_startRecording,
  17. api_stopRecording,
  18. } from "/src/helpers/communication";
  19. import state, { clearSelection, handleStopPlay, resetPlaybackToStart, togglePlay } from "/src/state";
  20. import { IPostMessage } from "/src/utils/native-message";
  21. import { usePageVisibility } from "@vant/use";
  22. export const evaluatingData = reactive({
  23. /** 评测数据 */
  24. contentData: {} as any,
  25. /** 评测模块是否加载完成 */
  26. rendered: false,
  27. earphone: false, // 是否插入耳机
  28. soundEffect: false, // 是否效音
  29. soundEffectFrequency: 0, // 效音频率
  30. checkStep: 0, // 执行步骤
  31. checkEnd: false, // 检测结束
  32. earphoneMode: false, // 耳机弹窗
  33. soundEffectMode: false, // 效音弹窗
  34. websocketState: false, // websocket连接状态
  35. startBegin: false, // 开始
  36. backtime: 0, // 延迟时间
  37. /** 已经评测的数据 */
  38. evaluatings: {} as IEvaluatings,
  39. /** 评测结果 */
  40. resultData: {} as any,
  41. /** 评测结果弹窗 */
  42. resulstMode: false,
  43. /** 是否是完整评测 */
  44. isComplete: false,
  45. });
  46. /** 点击开始评测按钮 */
  47. export const handleStartEvaluat = () => {
  48. if (state.modeType === "evaluating") {
  49. handleCancelEvaluat();
  50. } else {
  51. handleStopPlay();
  52. }
  53. state.modeType = state.modeType === "evaluating" ? "practise" : "evaluating";
  54. if (state.modeType !== "evaluating") {
  55. // 切换到练习模式,卸载评测模块
  56. evaluatingData.rendered = false;
  57. }
  58. };
  59. /** 开始播放发送延迟时间 */
  60. export const sendEvaluatingOffsetTime = (currentTime: number) => {
  61. const nowTime = Date.now();
  62. // console.log("第一次播放时间", evaluatingData.backtime);
  63. // console.log("已播放时长: ", currentTime, currentTime * 1000);
  64. // console.log("不减掉已播放时间: ", nowTime - evaluatingData.backtime , currentTime);
  65. const delayTime = (nowTime - evaluatingData.backtime - currentTime * 1000) / 1000;
  66. console.log("真正播放延迟", delayTime / 1000);
  67. // 蓝牙耳机延迟一点发送消息确保在录音后面
  68. setTimeout(async () => {
  69. await api_proxyServiceMessage({
  70. header: {
  71. commond: "audioPlayStart",
  72. type: "SOUND_COMPARE",
  73. },
  74. body: {
  75. offsetTime: delayTime < 0 ? 0 : delayTime,
  76. },
  77. });
  78. evaluatingData.backtime = 0;
  79. }, 220);
  80. };
  81. /** 检测耳机 */
  82. const checkUseEarphone = async () => {
  83. const res = await getEarphone();
  84. return res?.content?.checkIsWired || false;
  85. };
  86. /**
  87. * 开始录音
  88. */
  89. const handleStartSoundCheck = () => {
  90. startSoundCheck();
  91. };
  92. /** 结束录音 */
  93. export const handleEndSoundCheck = () => {
  94. endSoundCheck();
  95. };
  96. /** 连接websocket */
  97. export const connectWebsocket = async (content: any) => {
  98. evaluatingData.contentData = content;
  99. evaluatingData.websocketState = true;
  100. };
  101. /**
  102. * 执行检测
  103. */
  104. export const handlePerformDetection = async () => {
  105. evaluatingData.checkEnd = false;
  106. if (evaluatingData.checkStep === 0) {
  107. // 检测耳机
  108. const erji = await checkUseEarphone();
  109. evaluatingData.checkStep = 1;
  110. if (erji) {
  111. handlePerformDetection();
  112. } else {
  113. evaluatingData.earphoneMode = true;
  114. }
  115. return;
  116. }
  117. if (evaluatingData.checkStep === 1) {
  118. // 效音
  119. // 是否需要开启效音
  120. evaluatingData.checkStep = 10;
  121. if (state.setting.soundEffect) {
  122. evaluatingData.soundEffectMode = true;
  123. handleStartSoundCheck();
  124. } else {
  125. handlePerformDetection();
  126. }
  127. return;
  128. }
  129. if (evaluatingData.checkStep === 10) {
  130. // 连接websocket
  131. evaluatingData.checkEnd = true;
  132. evaluatingData.checkStep = 0;
  133. }
  134. };
  135. /** 记录小节分数 */
  136. export const addMeasureScore = (measureScore: any, show = true) => {
  137. evaluatingData.evaluatings[measureScore.measureRenderIndex] = {
  138. ...measureScore,
  139. leve: getLeveByScoreMeasure(measureScore.score),
  140. show,
  141. };
  142. // console.log("🚀 ~ measureScore:", evaluatingData.evaluatings[measureScore.measureRenderIndex])
  143. };
  144. const handleScoreResult = (res?: IPostMessage) => {
  145. if (res?.content) {
  146. console.log("🚀 ~ 评测返回:", res);
  147. const { header, body } = res.content;
  148. // 效音返回
  149. if (header.commond === "checking") {
  150. evaluatingData.soundEffectFrequency = body.frequency;
  151. }
  152. // 小节评分返回
  153. if (header?.commond === "measureScore") {
  154. addMeasureScore(body);
  155. }
  156. // 评测结束返回
  157. if (header?.commond === "overall") {
  158. // console.log("评测结束", body);
  159. evaluatingData.resulstMode = true;
  160. evaluatingData.resultData = {
  161. ...body,
  162. ...getLeveByScore(body.score),
  163. };
  164. // console.log("🚀 ~ evaluatingData.resultData:", evaluatingData.resultData)
  165. closeToast();
  166. }
  167. }
  168. };
  169. /** 开始评测 */
  170. export const handleStartBegin = async () => {
  171. evaluatingData.isComplete = false;
  172. evaluatingData.startBegin = true;
  173. evaluatingData.evaluatings = {};
  174. evaluatingData.resultData = {};
  175. evaluatingData.backtime = Date.now();
  176. resetPlaybackToStart();
  177. try {
  178. console.log("🚀 ~ content:", evaluatingData.contentData, JSON.stringify(evaluatingData.contentData));
  179. } catch (error) {}
  180. const res = await startEvaluating(evaluatingData.contentData);
  181. if (res?.api !== "startEvaluating") {
  182. Snackbar.error("请在APP端进行评测");
  183. evaluatingData.startBegin = false;
  184. return;
  185. }
  186. // 开始录音
  187. api_startRecording();
  188. const playState = await togglePlay("play");
  189. // 取消播放
  190. if (!playState) {
  191. evaluatingData.startBegin = false;
  192. handleCancelEvaluat();
  193. return;
  194. }
  195. };
  196. /**
  197. * 结束评测
  198. * @param isComplete 是否完整评测
  199. * @returns
  200. */
  201. export const handleEndEvaluat = (isComplete = false) => {
  202. console.log("触发结束");
  203. // 没有开始评测 , 不是评测模式 , 不评分
  204. if (!evaluatingData.startBegin || state.modeType !== "evaluating") return;
  205. evaluatingData.startBegin = false;
  206. // 结束录音
  207. api_stopRecording();
  208. // 结束评测
  209. endEvaluating({
  210. musicScoreId: state.examSongId,
  211. });
  212. showLoadingToast({
  213. message: "评分中",
  214. duration: 0,
  215. forbidClick: true,
  216. });
  217. evaluatingData.isComplete = isComplete;
  218. };
  219. /**
  220. * 结束评测
  221. * @param isEnd 是否是自动播放停止, 默认: false
  222. */
  223. export const handleEndBegin = () => {
  224. handleEndEvaluat();
  225. handleStopPlay();
  226. };
  227. /**
  228. * 取消评测
  229. */
  230. export const handleCancelEvaluat = () => {
  231. evaluatingData.evaluatings = {};
  232. // 关闭提示
  233. closeToast();
  234. // 取消记录
  235. api_proxyServiceMessage({
  236. header: {
  237. commond: "recordCancel",
  238. type: "SOUND_COMPARE",
  239. status: 200,
  240. },
  241. });
  242. // 取消评测
  243. cancelEvaluating();
  244. // 停止播放
  245. handleStopPlay();
  246. };
  247. /** 查看报告 */
  248. export const handleViewReport = (key: "recordId" | "recordIdStr", type: "gym" | "colexiu" | "orchestra") => {
  249. const id = evaluatingData.resultData?.[key] || "";
  250. let url = "";
  251. switch (type) {
  252. case "gym":
  253. url = location.origin + location.pathname + "report/" + id;
  254. break;
  255. case "orchestra":
  256. url = location.origin + location.pathname + "report-share.html?id=" + id;
  257. break;
  258. default:
  259. url = location.origin + location.pathname + "report-share.html?id=" + id;
  260. break;
  261. }
  262. api_openWebView({
  263. url,
  264. orientation: 0,
  265. isHideTitle: true, // 此处兼容安卓,意思为隐藏全部头部
  266. statusBarTextColor: false,
  267. isOpenLight: true,
  268. });
  269. };
  270. export default defineComponent({
  271. name: "evaluating",
  272. setup() {
  273. const pageVisibility = usePageVisibility();
  274. watch(pageVisibility, (value) => {
  275. if (value == "hidden" && evaluatingData.startBegin) {
  276. handleEndBegin();
  277. }
  278. });
  279. onMounted(() => {
  280. evaluatingData.resultData = {};
  281. // evaluatingData.resultData = {...getLeveByScore(90), score: 30, intonation: 10, cadence: 30, integrity: 40}
  282. // console.log("🚀 ~ evaluatingData.resultData:", evaluatingData.resultData)
  283. evaluatingData.evaluatings = {};
  284. evaluatingData.soundEffectFrequency = 0;
  285. evaluatingData.checkStep = 0;
  286. evaluatingData.rendered = true;
  287. sendResult(handleScoreResult);
  288. if (!state.isSelectMeasureMode) {
  289. clearSelection();
  290. }
  291. });
  292. onUnmounted(() => {
  293. removeResult(handleScoreResult);
  294. });
  295. return () => <div></div>;
  296. },
  297. });