import styles from "./index.module.less"; import { Snackbar } from "@varlet/ui"; import { closeToast, showLoadingToast, showToast, Popup } from "vant"; import { defineComponent, onMounted, onUnmounted, reactive, ref, watch } from "vue"; import { getLeveByScore, getLeveByScoreMeasure, IEvaluatings } from "./evaluatResult"; import { cancelEvaluating, endEvaluating, endSoundCheck, getEarphone, api_proxyServiceMessage, removeResult, sendResult, startEvaluating, startSoundCheck, api_openWebView, api_startRecording, api_stopRecording, api_recordStartTime, api_remove_recordStartTime, api_startCapture, api_endCapture, api_getDeviceDelay, hideComplexButton, api_checkSocketStatus, addAccompanyError, removeAccompanyError, addSocketStatus, removeSocketStatus, api_disconnectSocket, } from "/src/helpers/communication"; import state, { IPlayState, clearSelection, handleStopPlay, onPlay, resetPlaybackToStart, togglePlay, } from "/src/state"; import { IPostMessage } from "/src/utils/native-message"; import { usePageVisibility } from "@vant/use"; import { browser } from "/src/utils"; import { getAudioCurrentTime, toggleMutePlayAudio } from "../audio-list"; import { handleStartTick } from "../tick"; import AbnormalPop from "../abnormal-pop"; import { storeData } from "../../store"; const browserInfo = browser(); let socketStartTime = 0 export const evaluatingData = reactive({ /** 评测数据 */ contentData: {} as any, /** 评测模块是否加载完成 */ rendered: false, earphone: false, // 是否插入耳机 soundEffect: false, // 是否效音 soundEffectFrequency: 0, // 效音频率 checkStep: 0, // 执行步骤 checkEnd: false, // 检测结束 earphoneMode: false, // 耳机弹窗 soundEffectMode: false, // 效音弹窗 websocketState: false, // websocket连接状态 /**是否开始播放 */ startBegin: false, // 开始 backtime: 0, // 延迟时间 /** 已经评测的数据 */ evaluatings: {} as IEvaluatings, /** 评测结果 */ resultData: {} as any, /** 评测结果弹窗 */ resulstMode: false, /** 是否是完整评测 */ isComplete: false, /** */ isDisabledPlayMusic: false, /** socket异常状态弹窗 */ socketErrorPop: false, /** 异常提示 */ errorContents: '', /** socket异常状态弹窗的状态值 */ socketErrorStatus: 0, /** 延迟检测,socket状态异常 */ delayCheckSocketError: false, }); /** 点击开始评测按钮 */ export const handleStartEvaluat = async () => { if (state.modeType === "evaluating") { handleCancelEvaluat(); } else { if (state.platform !== 'PC') { // 评测前先检查APP端的websocket状态 const res = await api_checkSocketStatus(); if (res?.content?.status === "connected") { handleStopPlay(); } else { // socket未连接 // evaluatingData.socketErrorPop = true } } else { handleStopPlay(); } } state.modeType = state.modeType === "evaluating" ? "practise" : "evaluating"; if (state.modeType !== "evaluating") { // 切换到练习模式,卸载评测模块 evaluatingData.rendered = false; } }; /** 开始评测 & 延迟检测开始按钮 */ export const startCheckDelay = async () => { // 评测前先检查APP端的websocket状态 const res = await api_checkSocketStatus(); if (res?.content?.status === "connected") { // return new Promise((resolve) => { resolve({checked: true}) }); } else { /** * socket未连接,记录此时的时间,以便于和收到socket成功链接,进行对比,对比时间小于500ms时,则连接中的状态默认显示500ms持续时间 * * */ socketStartTime = +new Date() evaluatingData.socketErrorPop = true evaluatingData.socketErrorStatus = 1 return new Promise((resolve) => { resolve({checked: false}) }); } } const check_currentTime = () => { let preTime = 0; // 选段评测模式 if (state.isSelectMeasureMode) { preTime = state.section[0].time * 1000; } const currentTime = getAudioCurrentTime() * 1000 - preTime; // console.log('播放进度music', currentTime, 'preTime:' + preTime) if (currentTime >= 500) { sendEvaluatingOffsetTime(500); return; } setTimeout(() => { check_currentTime(); }, 10); }; /** 开始播放发送延迟时间 */ export const sendEvaluatingOffsetTime = async (currentTime: number) => { // 没有开始时间点, 不处理 if (!evaluatingData.backtime) return; const nowTime = Date.now(); const delayTime = nowTime - evaluatingData.backtime - currentTime; console.error("真正播放延迟", delayTime, "currentTime:", currentTime); await api_proxyServiceMessage({ header: { commond: "audioPlayStart", type: "SOUND_COMPARE", }, body: { offsetTime: delayTime < 0 ? 0 : delayTime, micDelay: 0, }, }); }; /** 检测耳机 */ const checkUseEarphone = async () => { const res = await getEarphone(); return res?.content?.checkIsWired || false; }; /** * 开始录音 */ const handleStartSoundCheck = () => { startSoundCheck(); }; /** 结束录音 */ export const handleEndSoundCheck = () => { endSoundCheck(); }; /** 连接websocket */ export const connectWebsocket = async (content: any) => { evaluatingData.contentData = content; evaluatingData.websocketState = true; }; /** * 执行检测 */ export const handlePerformDetection = async () => { // 检测完成不检测了 if (evaluatingData.checkEnd) return; // 延迟检测 if (evaluatingData.checkStep === 0) { evaluatingData.checkStep = 5; // 没有设备延迟数据 或 开启了效音 显示检测组件,并持续检测耳机状态 if (state.setting.soundEffect) { evaluatingData.soundEffectMode = true; return; } const delayTime = await api_getDeviceDelay(); console.log("🚀 ~ delayTime:", delayTime); if (!delayTime) { evaluatingData.soundEffectMode = true; return; } handlePerformDetection(); return; } // 检测耳机 if ((evaluatingData.checkStep = 5)) { evaluatingData.checkStep = 10; const erji = await checkUseEarphone(); if (!erji) { evaluatingData.earphoneMode = true; return; } handlePerformDetection(); return; } // 效音 // if (evaluatingData.checkStep === 7) { // // 是否需要开启效音 // evaluatingData.checkStep = 10; // if (state.setting.soundEffect && !state.isPercussion) { // evaluatingData.soundEffectMode = true; // handleStartSoundCheck(); // return // } // handlePerformDetection(); // return; // } // 效验完成 if (evaluatingData.checkStep === 10) { evaluatingData.checkEnd = true; } }; /** 记录小节分数 */ export const addMeasureScore = (measureScore: any, show = true) => { // #8720 bug修复 for(let idx in evaluatingData.evaluatings) { evaluatingData.evaluatings[idx].show = false } evaluatingData.evaluatings[measureScore.measureRenderIndex] = { ...measureScore, leve: getLeveByScoreMeasure(measureScore.score), show, }; // console.log("🚀 ~ measureScore:", evaluatingData.evaluatings) }; const handleScoreResult = (res?: IPostMessage) => { console.log('返回', res) if (res?.content) { const { header, body } = res.content; // 效音返回 if (header.commond === "checking") { evaluatingData.soundEffectFrequency = body.frequency; } // 小节评分返回 if (header?.commond === "measureScore") { console.log("🚀 ~ 评测返回:", res); addMeasureScore(body); } // 评测结束返回 if (header?.commond === "overall") { console.log("🚀 ~ 评测返回:", res); // console.log("评测结束", body); state.isHideEvaluatReportSaveBtn = false; evaluatingData.resulstMode = true; evaluatingData.resultData = { ...body, ...getLeveByScore(body.score), }; // console.log("🚀 ~ evaluatingData.resultData:", evaluatingData.resultData) closeToast(); } } }; /** 开始评测 */ export const handleStartBegin = async (preTimes?: number) => { evaluatingData.isComplete = false; evaluatingData.evaluatings = {}; evaluatingData.resultData = {}; evaluatingData.backtime = 0; resetPlaybackToStart(); const res = await startEvaluating(evaluatingData.contentData); if (res?.api !== "startEvaluating") { Snackbar.error("请在APP端进行评测"); evaluatingData.startBegin = false; return; } if (res?.content?.reson) { showToast(res.content?.des); evaluatingData.startBegin = false; return; } evaluatingData.startBegin = true; if (evaluatingData.isDisabledPlayMusic) { state.playState = state.playState === "paused" ? "play" : "paused"; // 设置为开始播放时, 如果需要节拍,先播放节拍器 if (state.playState === "play" && state.needTick) { const tickend = await handleStartTick(); // console.log("🚀 ~ tickend:", tickend) // 节拍器返回false, 取消播放 if (!tickend) { state.playState = "paused"; evaluatingData.startBegin = false; return; } } onPlay(); } //开始录音 await api_startRecording({ accompanimentState: state.setting.enableAccompaniment ? 1 : 0, firstNoteTime: preTimes || 0, }); // 如果开启了摄像头, 开启录制视频 if (state.setting.camera) { console.log("开始录制视频"); api_startCapture(); } }; /** 播放音乐 */ const playMusic = async () => { const playState = await togglePlay("play"); // 取消播放,停止播放 if (!playState) { evaluatingData.startBegin = false; handleCancelEvaluat(); return; } // 检测播放进度, 计算延迟 check_currentTime(); // 如果开启了摄像头, 开启录制视频 if (state.setting.camera) { console.log("开始录制视频"); api_startCapture(); } }; let _audio: HTMLAudioElement; /** 录音开始,记录开始时间点 */ const recordStartTimePoint = async (res?: IPostMessage) => { console.error("开始录音"); // 没有开始评测,不处理 if (!evaluatingData.startBegin) return; let inteveral = res?.content?.inteveral || 0; if (browserInfo.ios) { inteveral *= 1000; } evaluatingData.backtime = inteveral || Date.now(); console.log( "🚀 ~ 开始时间点:", evaluatingData.backtime, "已经录的时间:", Date.now() - inteveral, "记录时间点:", Date.now() ); // 是否禁播 if (evaluatingData.isDisabledPlayMusic) { return; } // 开始播放 playMusic(); }; /** * 结束评测 * @param isComplete 是否完整评测 * @returns */ export const handleEndEvaluat = (isComplete = false) => { // 没有开始评测 , 不是评测模式 , 不评分 if (!evaluatingData.startBegin || state.modeType !== "evaluating") return; // 结束录音 // api_stopRecording(); // 结束评测 endEvaluating({ musicScoreId: state.examSongId, }); showLoadingToast({ message: "评分中", duration: 0, overlay: true, overlayClass: styles.scoreMode, }); setTimeout(() => { evaluatingData.startBegin = false; }, 500); evaluatingData.isComplete = isComplete; // 如果开启了摄像头, 结束录制视频 if (state.setting.camera) { console.log("结束录制视频"); api_endCapture(); } }; /** * 结束评测 */ export const handleEndBegin = () => { handleEndEvaluat(); handleStopPlay(); }; /** * 取消评测 */ export const handleCancelEvaluat = () => { evaluatingData.evaluatings = {}; evaluatingData.startBegin = false; // 关闭提示 closeToast(); // 取消记录 api_proxyServiceMessage({ header: { commond: "recordCancel", type: "SOUND_COMPARE", status: 200, }, }); // 取消评测 cancelEvaluating(); // 停止播放 handleStopPlay(); endEvaluating({ musicScoreId: state.examSongId, }); // 如果开启了摄像头, 结束录制视频 if (state.setting.camera) { console.log("结束录制视频"); api_endCapture(); } }; /** 查看报告 */ export const handleViewReport = ( key: "recordId" | "recordIdStr", type: "gym" | "colexiu" | "orchestra" | "instrument" ) => { const id = evaluatingData.resultData?.[key] || ""; let url = ""; switch (type) { case "gym": url = location.origin + location.pathname + "#/report/" + id; break; case "orchestra": url = location.origin + location.pathname + "report-share.html?id=" + id; break; case "instrument": url = location.origin + location.pathname + "#/evaluat-report?id=" + id; break; default: url = location.origin + location.pathname + "report-share.html?id=" + id; break; } api_openWebView({ url, orientation: 0, isHideTitle: true, // 此处兼容安卓,意思为隐藏全部头部 statusBarTextColor: false, isOpenLight: true, c_orientation: 0, }); }; // 隐藏存演奏按钮 const handleComplexButton = (res?: IPostMessage) => { console.log('监听是否隐藏保存按钮', res) if (res?.content) { const { header, body } = res.content; state.isHideEvaluatReportSaveBtn = true } }; // 检测到APP发送的异常信息 const handleAccompanyError = (res?: IPostMessage) => { console.log('异常信息返回', res) if (res?.content) { const { type, reson } = res.content; switch (type) { case "enterBackground": // App退到后台 case "playError": // 播放异常 case "socketError": // socket连接断开,评测中,则取消评测 // 延迟检测中 if (evaluatingData.soundEffectMode) { evaluatingData.socketErrorStatus = 0 evaluatingData.delayCheckSocketError = true evaluatingData.socketErrorPop = type === "socketError" ? true : false // api_checkSocketStatus() return } // 评测中 if (state.modeType === "evaluating" && evaluatingData.startBegin) { handleCancelEvaluat(); } evaluatingData.socketErrorStatus = 0 evaluatingData.socketErrorPop = type === "socketError" ? true : false break; case "recordError": // 录音异常 break; default: break; } } }; // 监测socket状态,是否已经成功连接 const handleSocketStatus = (res?: IPostMessage) => { if (res?.content?.status === "connected") { const currentTime = +new Date() evaluatingData.delayCheckSocketError = false const diffTime = currentTime - socketStartTime if (diffTime < 1000) { const remainingTime = 1000 - diffTime console.log(remainingTime,99999) setTimeout(() => { evaluatingData.socketErrorStatus = 2 }, remainingTime); } } } // 评测出现异常,再试一次 const hanldeConfirmPop = async () => { api_checkSocketStatus(); evaluatingData.socketErrorStatus = 1 socketStartTime = +new Date() } // 关闭异常弹窗 const hanldeClosePop = () => { evaluatingData.socketErrorPop = false evaluatingData.socketErrorStatus = 0 } export default defineComponent({ name: "evaluating", setup() { const pageVisibility = usePageVisibility(); // 需要记录的数据 const record_old_data = reactive({ /** 指法 */ finger: false, /** 原音伴奏 */ play_mode: "" as IPlayState, /** 评测是否要伴奏 */ enableAccompaniment: true, }); /** 记录状态 */ const hanlde_record = () => { // 取消指法 record_old_data.finger = state.setting.displayFingering; state.setting.displayFingering = false; // 切换为伴奏 record_old_data.play_mode = state.playSource; record_old_data.enableAccompaniment = state.setting.enableAccompaniment; // 如果关闭伴奏,评测静音 if (!record_old_data.enableAccompaniment) { console.log("关闭伴奏"); toggleMutePlayAudio(record_old_data.play_mode === "music" ? "music" : "background", true); } }; /** 还原状态 */ const handle_reduction = () => { // 还原指法 state.setting.displayFingering = record_old_data.finger; state.playSource = record_old_data.play_mode; // 如果关闭伴奏, 结束评测取消静音 if (!record_old_data.enableAccompaniment) { toggleMutePlayAudio(record_old_data.play_mode === "music" ? "music" : "background", false); } }; watch(pageVisibility, (value) => { if (value == "hidden" && evaluatingData.startBegin) { // handleEndBegin(); } }); watch( () => evaluatingData.socketErrorStatus, () => { if (evaluatingData.socketErrorStatus === 2) { setTimeout(() => { evaluatingData.socketErrorPop = false // evaluatingData.socketErrorStatus = 0 }, 1000); } } ); onMounted(() => { resetPlaybackToStart(); hanlde_record(); evaluatingData.resultData = {}; // evaluatingData.resulstMode = true; // evaluatingData.resultData = {...getLeveByScore(10), score: 10, intonation: 10, cadence: 30, integrity: 40} // console.log("🚀 ~ evaluatingData.resultData:", evaluatingData.resultData) evaluatingData.evaluatings = {}; evaluatingData.soundEffectFrequency = 0; evaluatingData.checkStep = 0; evaluatingData.rendered = true; sendResult(handleScoreResult); hideComplexButton(handleComplexButton, true); api_recordStartTime(recordStartTimePoint); addAccompanyError(handleAccompanyError); addSocketStatus(handleSocketStatus); // 不是选段模式评测, 就清空已选段 if (!state.isSelectMeasureMode) { clearSelection(); } console.log("加载评测模块成功"); }); onUnmounted(() => { evaluatingData.checkEnd = false; evaluatingData.rendered = false; resetPlaybackToStart(); removeResult(handleScoreResult); hideComplexButton(() => {}, false); api_remove_recordStartTime(recordStartTimePoint); handle_reduction(); removeAccompanyError(handleAccompanyError); removeSocketStatus(handleSocketStatus); api_disconnectSocket(); console.log("卸载评测模块成功"); }); return () => (