index.tsx 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. import { Popup, Skeleton } from "vant";
  2. import {
  3. computed,
  4. defineComponent,
  5. nextTick,
  6. onBeforeMount,
  7. onBeforeUnmount,
  8. onMounted,
  9. reactive,
  10. Transition,
  11. watch,
  12. watchEffect,
  13. } from "vue";
  14. import { formateTimes } from "../../helpers/formateMusic";
  15. import Metronome, { metronomeData } from "../../helpers/metronome";
  16. import state, {
  17. EnumMusicRenderType,
  18. evaluatCreateMusicPlayer,
  19. handleSetSpeed,
  20. IAudioState,
  21. IPlatform,
  22. isRhythmicExercises,
  23. resetPlaybackToStart,
  24. togglePlay,
  25. } from "/src/state";
  26. import { browser, setGlobalData } from "../../utils";
  27. import AudioList from "../../view/audio-list";
  28. import MusicScore, { resetMusicScore } from "../../view/music-score";
  29. import { sysMusicScoreAccompanimentQueryPage } from "../api";
  30. import EvaluatModel from "../evaluat-model";
  31. import HeaderTop from "../header-top";
  32. import styles from "./index.module.less";
  33. import {
  34. api_cloudLoading,
  35. api_keepScreenLongLight,
  36. api_openCamera,
  37. api_openWebView,
  38. api_setEventTracking,
  39. api_setRequestedOrientation,
  40. api_setStatusBarVisibility,
  41. isSpecialShapedScreen,
  42. } from "/src/helpers/communication";
  43. import { getQuery } from "/src/utils/queryString";
  44. import Evaluating, { evaluatingData } from "/src/view/evaluating";
  45. import MeasureSpeed from "/src/view/plugins/measure-speed";
  46. import { mappingVoicePart, subjectFingering } from "/src/view/fingering/fingering-config";
  47. import Fingering from "/src/view/fingering";
  48. import store from "store";
  49. import Tick, { handleInitTick } from "/src/view/tick";
  50. import FollowPractice, { followData } from "/src/view/follow-practice";
  51. import FollowModel from "../follow-model";
  52. import RecordingTime from "../custom-plugins/recording-time";
  53. import WorkIndex from "../custom-plugins/work-index";
  54. import TheMusicList from "../component/the-music-list";
  55. import { storeData } from "/src/store";
  56. import ViewFigner from "../view-figner";
  57. /** 需要处理频率的乐器
  58. * 120: 竖笛
  59. */
  60. const instrumentSubject = [120];
  61. const resetFrequency = (list: any[]) => {
  62. if (!instrumentSubject.includes(state.subjectId)) return list;
  63. for (let i = 0; i < list.length; i++) {
  64. if (list[i].prevFrequency) list[i].prevFrequency = list[i].prevFrequency * 2;
  65. if (list[i].frequency) list[i].frequency = list[i].frequency * 2;
  66. if (list[i].nextFrequency) list[i].nextFrequency = list[i].nextFrequency * 2;
  67. }
  68. return list;
  69. };
  70. /**
  71. * 乐器指法处理
  72. */
  73. const setNoteHalfTone = (list: any[]) => {
  74. const instrumentNames = ["melodica"];
  75. if (!state.fingeringInfo?.name || !instrumentNames.includes(state.fingeringInfo.name)) return list;
  76. for (let i = 0; i < list.length; i++) {
  77. const note = list[i];
  78. if (note.noteElement?.pitch?.accidentalXml) {
  79. const accidentalXml = note.noteElement?.pitch?.accidentalXml;
  80. if ([]) {
  81. }
  82. if (accidentalXml === "flat") {
  83. // note.realKey = note.realKey + 1;
  84. } else if (accidentalXml === "sharp") {
  85. // note.realKey = note.realKey + 1;
  86. }
  87. }
  88. }
  89. return list;
  90. };
  91. export default defineComponent({
  92. name: "music-list",
  93. setup() {
  94. const query: any = getQuery();
  95. const detailData = reactive({
  96. isLoading: true,
  97. skeletonLoading: true,
  98. paddingLeft: "",
  99. headerHide: false,
  100. fingerPreView: false,
  101. orientation: 0,
  102. });
  103. const getAPPData = async () => {
  104. const screenData = await isSpecialShapedScreen();
  105. if (screenData?.content) {
  106. // console.log("🚀 ~ screenData:", screenData.content);
  107. const { isSpecialShapedScreen, notchHeight } = screenData.content;
  108. if (isSpecialShapedScreen) {
  109. detailData.paddingLeft = 25 + "px";
  110. }
  111. }
  112. };
  113. onBeforeMount(() => {
  114. api_keepScreenLongLight();
  115. getAPPData();
  116. api_setStatusBarVisibility();
  117. const settting = store.get("musicscoresetting");
  118. if (settting) {
  119. state.setting = settting;
  120. if (state.setting.camera) {
  121. api_openCamera();
  122. }
  123. }
  124. });
  125. // console.log(route.params, query)
  126. /** 获取曲谱数据 */
  127. const getMusicInfo = (res: any) => {
  128. const index = query["part-index"] ? parseInt(query["part-index"] as string) : 0;
  129. const musicData = res.data.background[index] || {};
  130. const musicInfo = {
  131. ...res.data,
  132. music: musicData.audioFileUrl || res.data.audioFileUrl,
  133. accompany: musicData.metronomeUrl || res.data.metronomeUrl,
  134. musicSheetId: musicData.musicSheetId || res.data.id,
  135. track: musicData.track || res.data.track,
  136. };
  137. console.log("🚀 ~ musicInfo:", musicInfo);
  138. setState(musicInfo, index);
  139. setCustom();
  140. detailData.isLoading = false;
  141. };
  142. const setState = (data: any, index: number) => {
  143. state.appName = "COLEXIU";
  144. state.detailId = data.id;
  145. state.xmlUrl = data.xmlFileUrl;
  146. state.partIndex = index;
  147. state.subjectId = data.musicSubject;
  148. state.categoriesId = data.categoriesId;
  149. state.categoriesName = data.musicTagNames;
  150. state.enableEvaluation = data.canEvaluate ? true : false;
  151. state.examSongId = data.id + "";
  152. state.examSongName = data.musicSheetName;
  153. // 解析扩展字段
  154. if (data.extConfigJson) {
  155. try {
  156. state.extConfigJson = JSON.parse(data.extConfigJson as string);
  157. } catch (error) {
  158. console.error("解析扩展字段错误:", error);
  159. }
  160. }
  161. state.isOpenMetronome = data.mp3Type === "MP3_METRONOME" ? true : false;
  162. // 曲子包含节拍器,就不开启节拍器
  163. state.needTick = data.mp3Type === "MP3_METRONOME" ? false : true;
  164. state.isShowFingering = data.showFingering ? true : false;
  165. state.music = data.music;
  166. state.accompany = data.accompany;
  167. state.midiUrl = data.midiUrl;
  168. state.parentCategoriesId = data.musicTag;
  169. state.musicSheetCategoriesId = data.musicSheetCategoriesId;
  170. state.playMode = data.audioType === "MP3" ? "MP3" : "MIDI";
  171. state.originSpeed = state.speed = data.playSpeed;
  172. const track = data.code || data.track;
  173. state.track = track ? track.replace(/ /g, "").toLocaleLowerCase() : "";
  174. state.enableNotation = data.notation ? true : false;
  175. // 映射声部ID
  176. state.subjectId = mappingVoicePart((state.track as any) || state.subjectId, "INSTRUMENT");
  177. // console.log("🚀 ~ state.subjectId:", state.subjectId, state.track as any , state.subjectId)
  178. // 是否打击乐
  179. // state.isPercussion =
  180. // state.subjectId == 23 ||
  181. // state.subjectId == 113 ||
  182. // state.subjectId == 121 ||
  183. // isRhythmicExercises();
  184. // 设置指法
  185. state.fingeringInfo = subjectFingering(state.subjectId);
  186. console.log("🚀 ~ state.fingeringInfo:", state.fingeringInfo, state.subjectId, state.track);
  187. // 检测是否原音和伴奏都有
  188. if (!state.music || !state.accompany) {
  189. state.playSource = state.music ? "music" : "background";
  190. }
  191. // 如果是PC端,放大曲谱
  192. state.platform = query.platform?.toLocaleUpperCase() || "";
  193. if (state.platform === IPlatform.PC) {
  194. state.zoom = query.zoom || 1.5;
  195. }
  196. //课堂乐器,默认简谱
  197. state.musicRenderType = EnumMusicRenderType.firstTone;
  198. };
  199. const setCustom = () => {
  200. if (state.extConfigJson.multitrack) {
  201. setGlobalData("multitrack", state.extConfigJson.multitrack);
  202. }
  203. };
  204. onMounted(() => {
  205. (window as any).appName = "colexiu";
  206. const id = query.id || "43554";
  207. Promise.all([sysMusicScoreAccompanimentQueryPage(id)]).then((values) => {
  208. getMusicInfo(values[0]);
  209. });
  210. api_setEventTracking();
  211. });
  212. /** 渲染完成 */
  213. const handleRendered = (osmd: any) => {
  214. detailData.skeletonLoading = false;
  215. state.osmd = osmd;
  216. // 没有设置速度使用读取的速度
  217. if (state.originSpeed === 0) {
  218. state.originSpeed = state.speed = (osmd as any).bpm || osmd.Sheet.userStartTempoInBPM || 100;
  219. }
  220. const saveSpeed =
  221. (store.get("speeds") || {})[state.examSongId] ||
  222. (osmd as any).bpm ||
  223. osmd.Sheet.userStartTempoInBPM;
  224. // 加载本地缓存的速度
  225. if (saveSpeed) {
  226. handleSetSpeed(saveSpeed);
  227. }
  228. state.times = formateTimes(osmd);
  229. state.times = resetFrequency(state.times);
  230. // state.times = setNoteHalfTone(state.times);
  231. console.log("🚀 ~ state.times:", state.times, state.subjectId);
  232. try {
  233. metronomeData.metro = new Metronome();
  234. metronomeData.metro.init(state.times);
  235. } catch (error) {}
  236. // 设置节拍器
  237. if (state.needTick) {
  238. const beatLengthInMilliseconds = (60 / state.speed) * 1000;
  239. // console.log(state.speed, osmd?.Sheet?.SheetPlaybackSetting?.beatLengthInMilliseconds , (60 / state.speed) * 1000)
  240. handleInitTick(
  241. beatLengthInMilliseconds,
  242. osmd?.Sheet?.SheetPlaybackSetting?.Rhythm?.Numerator || 4
  243. );
  244. }
  245. api_cloudLoading();
  246. state.musicRendered = true;
  247. evaluatCreateMusicPlayer();
  248. resetPlaybackToStart();
  249. };
  250. /** 指法配置 */
  251. const fingerConfig = computed<any>(() => {
  252. if (state.setting.displayFingering && state.fingeringInfo?.name) {
  253. if (state.fingeringInfo.direction === "transverse") {
  254. return {
  255. container: {
  256. paddingBottom: state.fingeringInfo.height,
  257. },
  258. fingerBox: {
  259. height: state.fingeringInfo.height,
  260. },
  261. };
  262. } else {
  263. return {
  264. container: {
  265. paddingRight: state.fingeringInfo.width,
  266. },
  267. fingerBox: {
  268. position: "absolute",
  269. width: state.fingeringInfo.width,
  270. height: "100%",
  271. right: 0,
  272. top: 0,
  273. },
  274. };
  275. }
  276. }
  277. return {
  278. container: {},
  279. fingerBox: {},
  280. };
  281. });
  282. // 监听指法显示
  283. watch(
  284. () => state.setting.displayFingering,
  285. () => {
  286. if (state.fingeringInfo.direction === "vertical") {
  287. nextTick(() => {
  288. resetMusicScore();
  289. });
  290. }
  291. }
  292. );
  293. watch(
  294. () => state.setting.soundEffect,
  295. () => {
  296. store.set("musicscoresetting", state.setting);
  297. }
  298. );
  299. /**播放状态改变时,向父页面发送事件 */
  300. const sendParentMessage = (playState: IAudioState) => {
  301. window.parent.postMessage(
  302. {
  303. api: "headerTogge",
  304. playState: playState,
  305. },
  306. "*"
  307. );
  308. };
  309. // 监听播放状态
  310. watch(
  311. () => state.playState,
  312. () => {
  313. if (state.platform != IPlatform.PC) {
  314. detailData.headerHide = state.playState === "play" ? true : false;
  315. }
  316. sendParentMessage(state.playState);
  317. }
  318. );
  319. /** 指法预览切换 */
  320. watch(() => detailData.fingerPreView, () => {
  321. console.log(2342)
  322. window.parent.postMessage(
  323. {
  324. api: "api_fingerPreView",
  325. state: detailData.fingerPreView,
  326. },
  327. "*"
  328. );
  329. })
  330. onMounted(() => {
  331. window.addEventListener("resize", resetMusicScore);
  332. });
  333. onBeforeUnmount(() => {
  334. window.removeEventListener("resize", resetMusicScore);
  335. });
  336. const browsInfo = browser();
  337. const handleOpenFignerView = () => {
  338. if (!query.modelType) {
  339. detailData.orientation = state.fingeringInfo.orientation || 0;
  340. api_setRequestedOrientation(detailData.orientation);
  341. }
  342. // const url = `${
  343. // /(192|localhost)/.test(location.origin)
  344. // ? "http://192.168.3.114:3000/instrument.html"
  345. // : location.origin + location.pathname
  346. // }#/view-figner`;
  347. // console.log("🚀 ~ url:", url);
  348. // api_openWebView({
  349. // url: url,
  350. // orientation: state.fingeringInfo.orientation || 0,
  351. // });
  352. if (state.playState === 'play') {
  353. togglePlay('paused')
  354. setTimeout(() => {
  355. detailData.fingerPreView = true;
  356. }, 500)
  357. return
  358. }
  359. detailData.fingerPreView = true;
  360. };
  361. const handleCloseFignerView = () => {
  362. if (!query.modelType && detailData.orientation == 1) {
  363. api_setRequestedOrientation(0);
  364. }
  365. detailData.fingerPreView = false;
  366. };
  367. return () => (
  368. <div
  369. class={[
  370. styles.detail,
  371. state.setting.eyeProtection && "eyeProtection",
  372. state.platform === IPlatform.PC && styles.PC,
  373. ]}
  374. style={{
  375. paddingLeft: detailData.paddingLeft,
  376. opacity: state.setting.camera ? `${state.setting.cameraOpacity / 100}` : "",
  377. }}
  378. >
  379. <Transition name="van-fade">
  380. {detailData.skeletonLoading && (
  381. <div class={styles.skeleton}>
  382. <Skeleton row={8} />
  383. </div>
  384. )}
  385. </Transition>
  386. <div class={[styles.headHeight, detailData.headerHide && styles.headHide]}>
  387. {state.musicRendered && <HeaderTop />}
  388. </div>
  389. <div
  390. id="scrollContainer"
  391. style={{ ...fingerConfig.value.container, height: detailData.headerHide ? "100vh" : "" }}
  392. class={[
  393. styles.container,
  394. !state.setting.displayCursor && "hideCursor",
  395. browsInfo.xiaomi && styles.xiaomi,
  396. ]}
  397. onClick={(e: Event) => {
  398. if (state.playState === "play" && state.platform != IPlatform.PC) {
  399. detailData.headerHide = !detailData.headerHide;
  400. }
  401. }}
  402. >
  403. {/* 曲谱渲染 */}
  404. {!detailData.isLoading && <MusicScore onRendered={handleRendered} />}
  405. {/* 指法 */}
  406. {state.setting.displayFingering && state.fingeringInfo?.name && (
  407. <div style={{ ...fingerConfig.value.fingerBox }}>
  408. <Fingering onOpen={() => handleOpenFignerView()} />
  409. </div>
  410. )}
  411. </div>
  412. {/* 节拍器 */}
  413. {state.needTick && <Tick />}
  414. {/* 播放 */}
  415. {!detailData.isLoading && <AudioList />}
  416. {/* 评测 */}
  417. {state.modeType === "evaluating" && (
  418. <>
  419. <Evaluating />
  420. {evaluatingData.rendered && <EvaluatModel />}
  421. </>
  422. )}
  423. {/* 跟练模式 */}
  424. {state.modeType === "follow" && (
  425. <>
  426. <FollowPractice />
  427. <FollowModel />
  428. </>
  429. )}
  430. {state.musicRendered && (
  431. <>
  432. {/* 统计训练时长 */}
  433. {storeData.isApp && <RecordingTime />}
  434. {/* 作业 */}
  435. {query.workRecord && <WorkIndex />}
  436. {/* 曲谱列表 */}
  437. {state.playState == "play" ||
  438. followData.start ||
  439. evaluatingData.startBegin ||
  440. query.workRecord ||
  441. query.modelType ||
  442. state.platform === IPlatform.PC ? null : (
  443. <TheMusicList />
  444. )}
  445. </>
  446. )}
  447. <Popup teleport="body" v-model:show={detailData.fingerPreView} position="bottom">
  448. <ViewFigner
  449. subject={state.fingeringInfo.name}
  450. isComponent={true}
  451. onClose={handleCloseFignerView}
  452. />
  453. </Popup>
  454. </div>
  455. );
  456. },
  457. });