import { Popup, Snackbar } from "@varlet/ui"; import { Transition, defineComponent, onMounted, reactive, watch } from "vue"; import { connectWebsocket, evaluatingData, handleEndBegin, handleEndSoundCheck, handlePerformDetection, handleStartBegin, handleStartEvaluat, handleViewReport, } from "/src/view/evaluating"; import Earphone from "./earphone"; import styles from "./index.module.less"; import SoundEffect from "./sound-effect"; import state from "/src/state"; import { storeData } from "/src/store"; import { browser } from "/src/utils"; import { getNoteByMeasuresSlursStart } from "/src/helpers/formateMusic"; import EvaluatResult from "./evaluat-result"; import EvaluatAudio from "./evaluat-audio"; import { api_openWebView, api_proxyServiceMessage, api_setCache, api_videoUpdate } from "/src/helpers/communication"; import EvaluatShare from "./evaluat-share"; import { Vue3Lottie } from "vue3-lottie"; import startData from "./data/start.json"; import startingData from "./data/starting.json"; import iconEvaluat from "./icons/evaluating.json"; import { getQuery } from "/src/utils/queryString"; import { showToast } from "vant"; // frequency 频率, amplitude 振幅, decibels 分贝 type TCriteria = "frequency" | "amplitude" | "decibels"; export default defineComponent({ name: "evaluat-model", setup() { const query = getQuery(); const evaluatModel = reactive({ tips: true, evaluatUpdateAudio: false, shareMode: false, }); const browserInfo = browser(); /** 是否是节奏练习 */ const isRhythmicExercises = () => { const examSongName = state.examSongName || ""; return examSongName.indexOf("节奏练习") > -1; }; /** 获取评测标准 */ const getEvaluationCriteria = () => { let criteria: TCriteria = "frequency"; // 声部打击乐 if ([23, 113, 121].includes(state.subjectId)) { criteria = "amplitude"; } else if (isRhythmicExercises()) { // 分类为节奏练习 criteria = "decibels"; } return criteria; }; /** 生成评测曲谱数据 */ const formatTimes = () => { const difftime = state.times[0]?.difftime || 0; let starTime = 0; let ListenMode = false; let dontEvaluatingMode = false; let skip = false; const datas = []; let times = state.times; /** 如果为选段模式,评测 */ if (state.isSelectMeasureMode) { const startIndex = state.times.findIndex((n: any) => n.noteId == state.section[0].noteId); const endIndex = state.times.findIndex((n: any) => n.noteId == state.section[1].noteId); times = state.times.filter((n: any, index: number) => { return index >= startIndex && index <= endIndex; }); starTime = times[0].sourceRelativeTime || times[0].relativeTime; } let measureIndex = -1; let recordMeasure = -1; for (let index = 0; index < times.length; index++) { const item = times[index]; const note = getNoteByMeasuresSlursStart(item); const rate = state.speed / state.originSpeed; const start = difftime + (item.sourceRelativeTime || item.relativeTime) - starTime; const end = difftime + (item.sourceRelaEndtime || item.relaEndtime) - starTime; const isStaccato = note.noteElement.voiceEntry.isStaccato(); const noteRate = isStaccato ? 0.5 : 1; if (note.formatLyricsEntries.contains("Play") || note.formatLyricsEntries.contains("Play...")) { ListenMode = false; } if (note.formatLyricsEntries.contains("Listen")) { ListenMode = true; } if (note.formatLyricsEntries.contains("纯律结束")) { dontEvaluatingMode = false; } if (note.formatLyricsEntries.contains("纯律")) { dontEvaluatingMode = true; } const nextNote = state.times[index + 1]; // console.log("noteinfo", note.noteElement.isRestFlag && !!note.stave && !!nextNote) if (skip && (note.stave || !item.noteElement.isRestFlag || (nextNote && !nextNote.noteElement.isRestFlag))) { skip = false; } if (note.noteElement.isRestFlag && !!note.stave && !!nextNote && nextNote.noteElement.isRestFlag) { skip = true; } // console.log(note.measureOpenIndex, item.measureOpenIndex, note); // console.log("skip", skip) if (note.measureOpenIndex != recordMeasure) { measureIndex++; recordMeasure = note.measureOpenIndex; } const data = { timeStamp: (start * 1000) / rate, duration: ((end * 1000) / rate - (start * 1000) / rate) * noteRate, frequency: item.frequency, nextFrequency: item.nextFrequency, prevFrequency: item.prevFrequency, // 重复的情况index会自然累加,render的index是谱面渲染的index measureIndex: measureIndex, measureRenderIndex: item.measureListIndex, dontEvaluating: ListenMode || dontEvaluatingMode || item.skipMode, musicalNotesIndex: index, denominator: note.noteElement?.Length.denominator, isOrnament: !!note?.voiceEntry?.ornamentContainer, }; datas.push(data); } return datas; }; /** 生成数据 */ const hanldeCreateData = async () => { const behaviorId = localStorage.getItem("behaviorId") || undefined; const rate = state.speed / state.originSpeed; const content = { musicXmlInfos: formatTimes(), subjectId: state.subjectId, detailId: state.detailId, examSongId: state.examSongId, xmlUrl: state.xmlUrl, partIndex: state.partIndex, behaviorId, platform: browserInfo.ios ? "IOS" : browserInfo.android ? "ANDROID" : "WEB", clientId: storeData.platformType === "STUDENT" ? "student" : storeData.platformType === "TEACHER" ? "teacher" : "education", hertz: state.setting.frequency, reactionTimeMs: state.setting.reactionTimeMs, speed: state.speed, heardLevel: state.setting.evaluationDifficulty, beatLength: Math.round((state.fixtime * 1000) / rate), practiceSource: query.unitId ? "UNIT_TEST" : "PRACTICE", feature: "EVALUATION", // evaluationCriteria: getEvaluationCriteria(), }; await connectWebsocket(content); // 切换为伴奏 if (state.accompany) { state.playSource = "background"; } console.log("连接成功"); }; /** 评测结果按钮处理 */ const handleEvaluatResult = (type: "practise" | "tryagain" | "look" | "share" | "update") => { if (type === "update") { // 上传云端 evaluatModel.evaluatUpdateAudio = true; return; } else if (type === "share") { // 分享 evaluatModel.shareMode = true; return; } else if (type === "look") { // 跳转 handleViewReport("recordIdStr", "orchestra"); return; } else if (type === "practise") { // 去练习 handleStartEvaluat(); } else if (type === "tryagain") { // 再来一次 } evaluatingData.resulstMode = false; }; /** 上传音视频 */ const hanldeUpdateVideoAndAudio = async (update = false) => { if (!update) { evaluatModel.evaluatUpdateAudio = false; return; } if (state.setting.camera && state.setting.saveToAlbum) { evaluatModel.evaluatUpdateAudio = false; api_videoUpdate((res: any) => { if (res) { if (res?.content?.type === "success") { handleSaveResult({ recordId: evaluatingData.resultData?.recordIdStr, filePath: res?.content?.filePath, }); } else if (res?.content?.type === "error") { showToast({ message: res.content?.message || "上传失败", }); } } }); return; } evaluatModel.evaluatUpdateAudio = false; handleSaveResult({ recordId: evaluatingData.resultData?.recordId, }); }; const handleSaveResult = (_body: any) => { api_proxyServiceMessage({ header: { commond: "videoUpload", status: 200, type: "SOUND_COMPARE", }, body: _body, }); showToast({ message: "上传成功", }); }; onMounted(() => { handlePerformDetection(); }); watch( () => evaluatingData.checkEnd, () => { if (evaluatingData.checkEnd) { console.log("检测结束,生成评测数据"); hanldeCreateData(); } } ); /** 监听评测结束 */ watch( () => evaluatingData.resulstMode, () => { // 评测结束, 并且完整评测 if (evaluatingData.resulstMode && evaluatingData.isComplete) { /** 有单元测验时,存储分数缓存 */ api_setCache({ key: "h5-orchestra-unit", value: JSON.stringify({ musicId: query.id || "", unitId: query.unitId || "", questionId: query.questionId || "", score: evaluatingData.resultData?.score || 0, }), }); } } ); return () => (