index.tsx 7.6 KB

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